mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	New feature: Reserve and release Public IPs (#6046)
* Reserve and release a public IP * Update #6046: show orange color for Reserved public ip * Update #6046 reserve IP: fix ui conflicts * Update #6046: fix resource count * Update #6046: associate Reserved public IP to network * Update #6046: fix unit tests * Update #6046: fix ui bugs * Update #6046: make api/ui available for domain admin and users
This commit is contained in:
		
							parent
							
								
									15e3a10f94
								
							
						
					
					
						commit
						6a53517d37
					
				| @ -133,6 +133,7 @@ public class EventTypes { | |||||||
|     // Network Events |     // Network Events | ||||||
|     public static final String EVENT_NET_IP_ASSIGN = "NET.IPASSIGN"; |     public static final String EVENT_NET_IP_ASSIGN = "NET.IPASSIGN"; | ||||||
|     public static final String EVENT_NET_IP_RELEASE = "NET.IPRELEASE"; |     public static final String EVENT_NET_IP_RELEASE = "NET.IPRELEASE"; | ||||||
|  |     public static final String EVENT_NET_IP_RESERVE = "NET.IPRESERVE"; | ||||||
|     public static final String EVENT_NET_IP_UPDATE = "NET.IPUPDATE"; |     public static final String EVENT_NET_IP_UPDATE = "NET.IPUPDATE"; | ||||||
|     public static final String EVENT_PORTABLE_IP_ASSIGN = "PORTABLE.IPASSIGN"; |     public static final String EVENT_PORTABLE_IP_ASSIGN = "PORTABLE.IPASSIGN"; | ||||||
|     public static final String EVENT_PORTABLE_IP_RELEASE = "PORTABLE.IPRELEASE"; |     public static final String EVENT_PORTABLE_IP_RELEASE = "PORTABLE.IPRELEASE"; | ||||||
|  | |||||||
| @ -41,6 +41,7 @@ public interface IpAddress extends ControlledEntity, Identity, InternalIdentity, | |||||||
|         Allocating, // The IP Address is being propagated to other network elements and is not ready for use yet. |         Allocating, // The IP Address is being propagated to other network elements and is not ready for use yet. | ||||||
|         Allocated, // The IP address is in used. |         Allocated, // The IP address is in used. | ||||||
|         Releasing, // The IP address is being released for other network elements and is not ready for allocation. |         Releasing, // The IP address is being released for other network elements and is not ready for allocation. | ||||||
|  |         Reserved,   // The IP address is reserved and is not ready for allocation. | ||||||
|         Free // The IP address is ready to be allocated. |         Free // The IP address is ready to be allocated. | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -59,6 +59,10 @@ public interface NetworkService { | |||||||
|     IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId, Boolean displayIp, String ipaddress) throws ResourceAllocationException, InsufficientAddressCapacityException, |     IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId, Boolean displayIp, String ipaddress) throws ResourceAllocationException, InsufficientAddressCapacityException, | ||||||
|         ConcurrentOperationException; |         ConcurrentOperationException; | ||||||
| 
 | 
 | ||||||
|  |     IpAddress reserveIpAddress(Account account, Boolean displayIp, Long ipAddressId) throws ResourceAllocationException; | ||||||
|  | 
 | ||||||
|  |     boolean releaseReservedIpAddress(long ipAddressId) throws InsufficientAddressCapacityException; | ||||||
|  | 
 | ||||||
|     boolean releaseIpAddress(long ipAddressId) throws InsufficientAddressCapacityException; |     boolean releaseIpAddress(long ipAddressId) throws InsufficientAddressCapacityException; | ||||||
| 
 | 
 | ||||||
|     IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long networkId, Long vpcId) throws ResourceAllocationException, |     IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long networkId, Long vpcId) throws ResourceAllocationException, | ||||||
|  | |||||||
| @ -0,0 +1,104 @@ | |||||||
|  | // 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.user.address; | ||||||
|  | 
 | ||||||
|  | import org.apache.log4j.Logger; | ||||||
|  | 
 | ||||||
|  | import org.apache.cloudstack.acl.RoleType; | ||||||
|  | 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.IPAddressResponse; | ||||||
|  | import org.apache.cloudstack.api.response.SuccessResponse; | ||||||
|  | import org.apache.cloudstack.context.CallContext; | ||||||
|  | 
 | ||||||
|  | import com.cloud.exception.InsufficientAddressCapacityException; | ||||||
|  | import com.cloud.exception.InvalidParameterValueException; | ||||||
|  | import com.cloud.network.IpAddress; | ||||||
|  | 
 | ||||||
|  | @APICommand(name = "releaseIpAddress", | ||||||
|  |         description = "Releases an IP address from the account.", | ||||||
|  |         since = "4.17", | ||||||
|  |         responseObject = SuccessResponse.class, | ||||||
|  |         requestHasSensitiveInfo = false, | ||||||
|  |         responseHasSensitiveInfo = false, | ||||||
|  |         authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) | ||||||
|  | public class ReleaseIPAddrCmd extends BaseCmd { | ||||||
|  |     public static final Logger s_logger = Logger.getLogger(ReleaseIPAddrCmd.class.getName()); | ||||||
|  | 
 | ||||||
|  |     private static final String s_name = "releaseipaddressresponse"; | ||||||
|  | 
 | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  |     //////////////// API parameters ///////////////////// | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  | 
 | ||||||
|  |     @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = IPAddressResponse.class, required = true, description = "the ID of the public IP address" | ||||||
|  |         + " to release") | ||||||
|  |     private Long id; | ||||||
|  | 
 | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  |     /////////////////// Accessors /////////////////////// | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  | 
 | ||||||
|  |     public Long getIpAddressId() { | ||||||
|  |         return id; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  |     /////////////// API Implementation/////////////////// | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public String getCommandName() { | ||||||
|  |         return s_name; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public long getEntityOwnerId() { | ||||||
|  |         IpAddress ip = getIpAddress(id); | ||||||
|  |         if (ip == null) { | ||||||
|  |             throw new InvalidParameterValueException("Unable to find IP address by ID=" + id); | ||||||
|  |         } | ||||||
|  |         return ip.getAccountId(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void execute() throws InsufficientAddressCapacityException { | ||||||
|  |         CallContext.current().setEventDetails("IP ID: " + getIpAddressId()); | ||||||
|  |         boolean result = _networkService.releaseReservedIpAddress(getIpAddressId()); | ||||||
|  |         if (result) { | ||||||
|  |             SuccessResponse response = new SuccessResponse(getCommandName()); | ||||||
|  |             this.setResponseObject(response); | ||||||
|  |         } else { | ||||||
|  |             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to release IP address"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private IpAddress getIpAddress(long id) { | ||||||
|  |         IpAddress ip = _entityMgr.findById(IpAddress.class, id); | ||||||
|  | 
 | ||||||
|  |         if (ip == null) { | ||||||
|  |             throw new InvalidParameterValueException("Unable to find IP address by ID=" + id); | ||||||
|  |         } else { | ||||||
|  |             return ip; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,164 @@ | |||||||
|  | // 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.user.address; | ||||||
|  | 
 | ||||||
|  | import org.apache.log4j.Logger; | ||||||
|  | 
 | ||||||
|  | import org.apache.cloudstack.acl.RoleType; | ||||||
|  | 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.ResponseObject.ResponseView; | ||||||
|  | import org.apache.cloudstack.api.ServerApiException; | ||||||
|  | import org.apache.cloudstack.api.command.user.UserCmd; | ||||||
|  | import org.apache.cloudstack.api.response.DomainResponse; | ||||||
|  | import org.apache.cloudstack.api.response.IPAddressResponse; | ||||||
|  | import org.apache.cloudstack.api.response.ProjectResponse; | ||||||
|  | import org.apache.cloudstack.context.CallContext; | ||||||
|  | 
 | ||||||
|  | import com.cloud.exception.ConcurrentOperationException; | ||||||
|  | import com.cloud.exception.InvalidParameterValueException; | ||||||
|  | import com.cloud.exception.PermissionDeniedException; | ||||||
|  | import com.cloud.exception.ResourceAllocationException; | ||||||
|  | import com.cloud.exception.ResourceUnavailableException; | ||||||
|  | import com.cloud.network.IpAddress; | ||||||
|  | import com.cloud.projects.Project; | ||||||
|  | import com.cloud.user.Account; | ||||||
|  | 
 | ||||||
|  | @APICommand(name = "reserveIpAddress", | ||||||
|  |         description = "Reserve a public IP to an account.", | ||||||
|  |         since = "4.17", | ||||||
|  |         responseObject = IPAddressResponse.class, | ||||||
|  |         responseView = ResponseView.Restricted, | ||||||
|  |         requestHasSensitiveInfo = false, | ||||||
|  |         responseHasSensitiveInfo = false, | ||||||
|  |         authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) | ||||||
|  | public class ReserveIPAddrCmd extends BaseCmd implements UserCmd { | ||||||
|  |     public static final Logger s_logger = Logger.getLogger(ReserveIPAddrCmd.class.getName()); | ||||||
|  |     private static final String s_name = "reserveipaddressresponse"; | ||||||
|  | 
 | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  |     //////////////// API parameters ///////////////////// | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  | 
 | ||||||
|  |     @Parameter(name = ApiConstants.ACCOUNT, | ||||||
|  |             type = CommandType.STRING, | ||||||
|  |             description = "the account to reserve with this IP address") | ||||||
|  |     private String accountName; | ||||||
|  | 
 | ||||||
|  |     @Parameter(name = ApiConstants.DOMAIN_ID, | ||||||
|  |             type = CommandType.UUID, | ||||||
|  |             entityType = DomainResponse.class, | ||||||
|  |             description = "the ID of the domain to reserve with this IP address") | ||||||
|  |     private Long domainId; | ||||||
|  | 
 | ||||||
|  |     @Parameter(name = ApiConstants.PROJECT_ID, | ||||||
|  |             type = CommandType.UUID, | ||||||
|  |             entityType = ProjectResponse.class, | ||||||
|  |             description = "the ID of the project to reserve with this IP address") | ||||||
|  |     private Long projectId; | ||||||
|  | 
 | ||||||
|  |     @Parameter(name = ApiConstants.FOR_DISPLAY, | ||||||
|  |             type = CommandType.BOOLEAN, | ||||||
|  |             description = "an optional field, whether to the display the IP to the end user or not", | ||||||
|  |             authorized = {RoleType.Admin}) | ||||||
|  |     private Boolean display; | ||||||
|  | 
 | ||||||
|  |     @Parameter(name = ApiConstants.ID, | ||||||
|  |             type = CommandType.UUID, | ||||||
|  |             entityType = IPAddressResponse.class, | ||||||
|  |             required = true, | ||||||
|  |             description = "the ID of the public IP address to reserve") | ||||||
|  |     private Long id; | ||||||
|  | 
 | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  |     /////////////////// Accessors /////////////////////// | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  | 
 | ||||||
|  |     public String getAccountName() { | ||||||
|  |         if (accountName != null) { | ||||||
|  |             return accountName; | ||||||
|  |         } | ||||||
|  |         return CallContext.current().getCallingAccount().getAccountName(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getDomainId() { | ||||||
|  |         if (domainId != null) { | ||||||
|  |             return domainId; | ||||||
|  |         } | ||||||
|  |         return CallContext.current().getCallingAccount().getDomainId(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public Long getIpAddressId() { | ||||||
|  |         return id; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean isDisplay() { | ||||||
|  |         if (display == null) | ||||||
|  |             return true; | ||||||
|  |         else | ||||||
|  |             return display; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public long getEntityOwnerId() { | ||||||
|  |         Account caller = CallContext.current().getCallingAccount(); | ||||||
|  |         if (accountName != null && domainId != null) { | ||||||
|  |             Account account = _accountService.finalizeOwner(caller, accountName, domainId, projectId); | ||||||
|  |             return account.getId(); | ||||||
|  |         } else if (projectId != null) { | ||||||
|  |             Project project = _projectService.getProject(projectId); | ||||||
|  |             if (project != null) { | ||||||
|  |                 if (project.getState() == Project.State.Active) { | ||||||
|  |                     return project.getProjectAccountId(); | ||||||
|  |                 } else { | ||||||
|  |                     throw new PermissionDeniedException("Can't add resources to the project with specified projectId in state=" + project.getState() + | ||||||
|  |                             " as it's no longer active"); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 throw new InvalidParameterValueException("Unable to find project by ID"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return caller.getAccountId(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  |     /////////////// API Implementation/////////////////// | ||||||
|  |     ///////////////////////////////////////////////////// | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public String getCommandName() { | ||||||
|  |         return s_name; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void execute() throws ResourceUnavailableException, ResourceAllocationException, ConcurrentOperationException { | ||||||
|  |         IpAddress result = _networkService.reserveIpAddress(_accountService.getAccount(getEntityOwnerId()), isDisplay(), getIpAddressId()); | ||||||
|  |         if (result != null) { | ||||||
|  |             IPAddressResponse ipResponse = _responseGenerator.createIPAddressResponse(getResponseView(), result); | ||||||
|  |             ipResponse.setResponseName(getCommandName()); | ||||||
|  |             setResponseObject(ipResponse); | ||||||
|  |         } else { | ||||||
|  |             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to reserve IP address"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -92,4 +92,6 @@ public interface IPAddressDao extends GenericDao<IPAddressVO, Long> { | |||||||
|     List<IPAddressVO> listByAssociatedVmId(long vmId); |     List<IPAddressVO> listByAssociatedVmId(long vmId); | ||||||
| 
 | 
 | ||||||
|     IPAddressVO findByVmIdAndNetworkId(long networkId, long vmId); |     IPAddressVO findByVmIdAndNetworkId(long networkId, long vmId); | ||||||
|  | 
 | ||||||
|  |     IPAddressVO findByAccountIdAndZoneIdAndStateAndIpAddress(long accountId, long dcId, State state, String ipAddress); | ||||||
| } | } | ||||||
|  | |||||||
| @ -62,6 +62,7 @@ public class IPAddressDaoImpl extends GenericDaoBase<IPAddressVO, Long> implemen | |||||||
|     @Inject |     @Inject | ||||||
|     protected VlanDao _vlanDao; |     protected VlanDao _vlanDao; | ||||||
|     protected GenericSearchBuilder<IPAddressVO, Long> CountFreePublicIps; |     protected GenericSearchBuilder<IPAddressVO, Long> CountFreePublicIps; | ||||||
|  |     protected SearchBuilder<IPAddressVO> PublicIpSearchByAccountAndState; | ||||||
|     @Inject |     @Inject | ||||||
|     ResourceTagDao _tagsDao; |     ResourceTagDao _tagsDao; | ||||||
|     @Inject |     @Inject | ||||||
| @ -138,6 +139,7 @@ public class IPAddressDaoImpl extends GenericDaoBase<IPAddressVO, Long> implemen | |||||||
|         AllocatedIpCountForAccount.and("allocated", AllocatedIpCountForAccount.entity().getAllocatedTime(), Op.NNULL); |         AllocatedIpCountForAccount.and("allocated", AllocatedIpCountForAccount.entity().getAllocatedTime(), Op.NNULL); | ||||||
|         AllocatedIpCountForAccount.and().op("network", AllocatedIpCountForAccount.entity().getAssociatedWithNetworkId(), Op.NNULL); |         AllocatedIpCountForAccount.and().op("network", AllocatedIpCountForAccount.entity().getAssociatedWithNetworkId(), Op.NNULL); | ||||||
|         AllocatedIpCountForAccount.or("vpc", AllocatedIpCountForAccount.entity().getVpcId(), Op.NNULL); |         AllocatedIpCountForAccount.or("vpc", AllocatedIpCountForAccount.entity().getVpcId(), Op.NNULL); | ||||||
|  |         AllocatedIpCountForAccount.or("state", AllocatedIpCountForAccount.entity().getState(), Op.EQ); | ||||||
|         AllocatedIpCountForAccount.cp();AllocatedIpCountForAccount.done(); |         AllocatedIpCountForAccount.cp();AllocatedIpCountForAccount.done(); | ||||||
| 
 | 
 | ||||||
|         CountFreePublicIps = createSearchBuilder(Long.class); |         CountFreePublicIps = createSearchBuilder(Long.class); | ||||||
| @ -152,6 +154,13 @@ public class IPAddressDaoImpl extends GenericDaoBase<IPAddressVO, Long> implemen | |||||||
|         DeleteAllExceptGivenIp = createSearchBuilder(); |         DeleteAllExceptGivenIp = createSearchBuilder(); | ||||||
|         DeleteAllExceptGivenIp.and("vlanDbId", DeleteAllExceptGivenIp.entity().getVlanId(), Op.EQ); |         DeleteAllExceptGivenIp.and("vlanDbId", DeleteAllExceptGivenIp.entity().getVlanId(), Op.EQ); | ||||||
|         DeleteAllExceptGivenIp.and("ip", DeleteAllExceptGivenIp.entity().getAddress(), Op.NEQ); |         DeleteAllExceptGivenIp.and("ip", DeleteAllExceptGivenIp.entity().getAddress(), Op.NEQ); | ||||||
|  | 
 | ||||||
|  |         PublicIpSearchByAccountAndState = createSearchBuilder(); | ||||||
|  |         PublicIpSearchByAccountAndState.and("accountId", PublicIpSearchByAccountAndState.entity().getAllocatedToAccountId(), Op.EQ); | ||||||
|  |         PublicIpSearchByAccountAndState.and("dcId", PublicIpSearchByAccountAndState.entity().getDataCenterId(), Op.EQ); | ||||||
|  |         PublicIpSearchByAccountAndState.and("state", PublicIpSearchByAccountAndState.entity().getState(), Op.EQ); | ||||||
|  |         PublicIpSearchByAccountAndState.and("allocated", PublicIpSearchByAccountAndState.entity().getAllocatedTime(), Op.NNULL); | ||||||
|  |         PublicIpSearchByAccountAndState.and("ipAddress", PublicIpSearchByAccountAndState.entity().getAddress(), Op.EQ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @ -368,6 +377,7 @@ public class IPAddressDaoImpl extends GenericDaoBase<IPAddressVO, Long> implemen | |||||||
|     public long countAllocatedIPsForAccount(long accountId) { |     public long countAllocatedIPsForAccount(long accountId) { | ||||||
|         SearchCriteria<Long> sc = AllocatedIpCountForAccount.create(); |         SearchCriteria<Long> sc = AllocatedIpCountForAccount.create(); | ||||||
|         sc.setParameters("account", accountId); |         sc.setParameters("account", accountId); | ||||||
|  |         sc.setParameters("state", State.Reserved); | ||||||
|         return customSearch(sc, null).get(0); |         return customSearch(sc, null).get(0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -480,4 +490,14 @@ public class IPAddressDaoImpl extends GenericDaoBase<IPAddressVO, Long> implemen | |||||||
|         sc.setParameters("state", state); |         sc.setParameters("state", state); | ||||||
|         return listBy(sc); |         return listBy(sc); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public IPAddressVO findByAccountIdAndZoneIdAndStateAndIpAddress(long accountId, long dcId, State state, String ipAddress) { | ||||||
|  |         SearchCriteria<IPAddressVO> sc = PublicIpSearchByAccountAndState.create(); | ||||||
|  |         sc.setParameters("accountId", accountId); | ||||||
|  |         sc.setParameters("dcId", dcId); | ||||||
|  |         sc.setParameters("state", state); | ||||||
|  |         sc.setParameters("ipAddress", ipAddress); | ||||||
|  |         return findOneBy(sc); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1239,6 +1239,13 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage | |||||||
|                 s_logger.debug("Associate IP address lock acquired"); |                 s_logger.debug("Associate IP address lock acquired"); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             if (ipaddress != null) { | ||||||
|  |                 IPAddressVO ipAddr = _ipAddressDao.findByAccountIdAndZoneIdAndStateAndIpAddress(ipOwner.getId(), zone.getId(), State.Reserved, ipaddress); | ||||||
|  |                 if (ipAddr != null) { | ||||||
|  |                     return PublicIp.createFromAddrAndVlan(ipAddr, _vlanDao.findById(ipAddr.getVlanId())); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             ip = Transaction.execute(new TransactionCallbackWithException<PublicIp, InsufficientAddressCapacityException>() { |             ip = Transaction.execute(new TransactionCallbackWithException<PublicIp, InsufficientAddressCapacityException>() { | ||||||
|                 @Override |                 @Override | ||||||
|                 public PublicIp doInTransaction(TransactionStatus status) throws InsufficientAddressCapacityException { |                 public PublicIp doInTransaction(TransactionStatus status) throws InsufficientAddressCapacityException { | ||||||
| @ -1401,7 +1408,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (ipToAssoc.getAssociatedWithNetworkId() != null) { |         if (ipToAssoc.getAssociatedWithNetworkId() != null) { | ||||||
|             s_logger.debug("IP " + ipToAssoc + " is already associated with network id" + networkId); |             s_logger.debug("IP " + ipToAssoc + " is already associated with network id=" + networkId); | ||||||
|             return ipToAssoc; |             return ipToAssoc; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -1459,6 +1466,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage | |||||||
| 
 | 
 | ||||||
|         IPAddressVO ip = _ipAddressDao.findById(ipId); |         IPAddressVO ip = _ipAddressDao.findById(ipId); | ||||||
|         //update ip address with networkId |         //update ip address with networkId | ||||||
|  |         ip.setState(State.Allocated); | ||||||
|         ip.setAssociatedWithNetworkId(networkId); |         ip.setAssociatedWithNetworkId(networkId); | ||||||
|         ip.setSourceNat(isSourceNat); |         ip.setSourceNat(isSourceNat); | ||||||
|         _ipAddressDao.update(ipId, ip); |         _ipAddressDao.update(ipId, ip); | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ import java.util.ArrayList; | |||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.Comparator; | import java.util.Comparator; | ||||||
|  | import java.util.Date; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @ -61,6 +62,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; | |||||||
| import org.apache.cloudstack.framework.messagebus.MessageBus; | import org.apache.cloudstack.framework.messagebus.MessageBus; | ||||||
| import org.apache.cloudstack.framework.messagebus.PublishScope; | import org.apache.cloudstack.framework.messagebus.PublishScope; | ||||||
| import org.apache.cloudstack.network.element.InternalLoadBalancerElementService; | import org.apache.cloudstack.network.element.InternalLoadBalancerElementService; | ||||||
|  | import org.apache.commons.collections.CollectionUtils; | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
| import org.apache.log4j.Logger; | import org.apache.log4j.Logger; | ||||||
| 
 | 
 | ||||||
| @ -68,14 +70,18 @@ import com.cloud.api.ApiDBUtils; | |||||||
| import com.cloud.configuration.Config; | import com.cloud.configuration.Config; | ||||||
| import com.cloud.configuration.ConfigurationManager; | import com.cloud.configuration.ConfigurationManager; | ||||||
| import com.cloud.configuration.Resource; | import com.cloud.configuration.Resource; | ||||||
|  | import com.cloud.dc.AccountVlanMapVO; | ||||||
| import com.cloud.dc.DataCenter; | import com.cloud.dc.DataCenter; | ||||||
| import com.cloud.dc.DataCenter.NetworkType; | import com.cloud.dc.DataCenter.NetworkType; | ||||||
| import com.cloud.dc.DataCenterVO; | import com.cloud.dc.DataCenterVO; | ||||||
| import com.cloud.dc.DataCenterVnetVO; | import com.cloud.dc.DataCenterVnetVO; | ||||||
|  | import com.cloud.dc.DomainVlanMapVO; | ||||||
| import com.cloud.dc.Vlan.VlanType; | import com.cloud.dc.Vlan.VlanType; | ||||||
| import com.cloud.dc.VlanVO; | import com.cloud.dc.VlanVO; | ||||||
|  | import com.cloud.dc.dao.AccountVlanMapDao; | ||||||
| import com.cloud.dc.dao.DataCenterDao; | import com.cloud.dc.dao.DataCenterDao; | ||||||
| import com.cloud.dc.dao.DataCenterVnetDao; | import com.cloud.dc.dao.DataCenterVnetDao; | ||||||
|  | import com.cloud.dc.dao.DomainVlanMapDao; | ||||||
| import com.cloud.dc.dao.VlanDao; | import com.cloud.dc.dao.VlanDao; | ||||||
| import com.cloud.deploy.DeployDestination; | import com.cloud.deploy.DeployDestination; | ||||||
| import com.cloud.domain.Domain; | import com.cloud.domain.Domain; | ||||||
| @ -84,6 +90,7 @@ import com.cloud.domain.dao.DomainDao; | |||||||
| import com.cloud.event.ActionEvent; | import com.cloud.event.ActionEvent; | ||||||
| import com.cloud.event.EventTypes; | import com.cloud.event.EventTypes; | ||||||
| import com.cloud.event.UsageEventUtils; | import com.cloud.event.UsageEventUtils; | ||||||
|  | import com.cloud.exception.AccountLimitException; | ||||||
| import com.cloud.exception.ConcurrentOperationException; | import com.cloud.exception.ConcurrentOperationException; | ||||||
| import com.cloud.exception.InsufficientAddressCapacityException; | import com.cloud.exception.InsufficientAddressCapacityException; | ||||||
| import com.cloud.exception.InsufficientCapacityException; | import com.cloud.exception.InsufficientCapacityException; | ||||||
| @ -303,6 +310,10 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C | |||||||
|     @Inject |     @Inject | ||||||
|     AccountGuestVlanMapDao _accountGuestVlanMapDao; |     AccountGuestVlanMapDao _accountGuestVlanMapDao; | ||||||
|     @Inject |     @Inject | ||||||
|  |     AccountVlanMapDao _accountVlanMapDao; | ||||||
|  |     @Inject | ||||||
|  |     DomainVlanMapDao _domainVlanMapDao; | ||||||
|  |     @Inject | ||||||
|     VpcDao _vpcDao; |     VpcDao _vpcDao; | ||||||
|     @Inject |     @Inject | ||||||
|     NetworkACLDao _networkACLDao; |     NetworkACLDao _networkACLDao; | ||||||
| @ -915,6 +926,98 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C | |||||||
|         return nicSecIp; |         return nicSecIp; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     @ActionEvent(eventType = EventTypes.EVENT_NET_IP_RESERVE, eventDescription = "reserving Ip", async = false) | ||||||
|  |     public IpAddress reserveIpAddress(Account account, Boolean displayIp, Long ipAddressId) throws ResourceAllocationException { | ||||||
|  |         IPAddressVO ipVO = _ipAddressDao.findById(ipAddressId); | ||||||
|  |         if (ipVO == null) { | ||||||
|  |             throw new InvalidParameterValueException("Unable to find IP address by ID=" + ipAddressId); | ||||||
|  |         } | ||||||
|  |         // verify permissions | ||||||
|  |         Account caller = CallContext.current().getCallingAccount(); | ||||||
|  |         _accountMgr.checkAccess(caller, null, true, account); | ||||||
|  | 
 | ||||||
|  |         VlanVO vlan = _vlanDao.findById(ipVO.getVlanId()); | ||||||
|  |         if (!vlan.getVlanType().equals(VlanType.VirtualNetwork)) { | ||||||
|  |             throw new IllegalArgumentException("only ip addresses that belong to a virtual network may be reserved."); | ||||||
|  |         } | ||||||
|  |         if (ipVO.isPortable()) { | ||||||
|  |             throw new InvalidParameterValueException("Unable to reserve a portable IP."); | ||||||
|  |         } | ||||||
|  |         if (State.Reserved.equals(ipVO.getState())) { | ||||||
|  |             if (account.getId() == ipVO.getAccountId()) { | ||||||
|  |                 s_logger.info(String.format("IP address %s has already been reserved for account %s", ipVO.getAddress(), account)); | ||||||
|  |                 return ipVO; | ||||||
|  |             } | ||||||
|  |             throw new InvalidParameterValueException("Unable to reserve a IP because it has already been reserved for another account."); | ||||||
|  |         } | ||||||
|  |         if (!State.Free.equals(ipVO.getState())) { | ||||||
|  |             throw new InvalidParameterValueException("Unable to reserve a IP in " + ipVO.getState() + " state."); | ||||||
|  |         } | ||||||
|  |         Long ipDedicatedDomainId = getIpDedicatedDomainId(ipVO.getVlanId()); | ||||||
|  |         if (ipDedicatedDomainId != null && !ipDedicatedDomainId.equals(account.getDomainId())) { | ||||||
|  |             throw new InvalidParameterValueException("Unable to reserve a IP because it is dedicated to another domain."); | ||||||
|  |         } | ||||||
|  |         Long ipDedicatedAccountId = getIpDedicatedAccountId(ipVO.getVlanId()); | ||||||
|  |         if (ipDedicatedAccountId != null && !ipDedicatedAccountId.equals(account.getAccountId())) { | ||||||
|  |             throw new InvalidParameterValueException("Unable to reserve a IP because it is dedicated to another account."); | ||||||
|  |         } | ||||||
|  |         if (ipDedicatedAccountId == null) { | ||||||
|  |             // Check that the maximum number of public IPs for the given accountId will not be exceeded | ||||||
|  |             try { | ||||||
|  |                 _resourceLimitMgr.checkResourceLimit(account, Resource.ResourceType.public_ip); | ||||||
|  |             } catch (ResourceAllocationException ex) { | ||||||
|  |                 s_logger.warn("Failed to allocate resource of type " + ex.getResourceType() + " for account " + account); | ||||||
|  |                 throw new AccountLimitException("Maximum number of public IP addresses for account: " + account.getAccountName() + " has been exceeded."); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         List<AccountVlanMapVO> maps = _accountVlanMapDao.listAccountVlanMapsByVlan(ipVO.getVlanId()); | ||||||
|  |         ipVO.setAllocatedTime(new Date()); | ||||||
|  |         ipVO.setAllocatedToAccountId(account.getAccountId()); | ||||||
|  |         ipVO.setAllocatedInDomainId(account.getDomainId()); | ||||||
|  |         ipVO.setState(State.Reserved); | ||||||
|  |         if (displayIp != null) { | ||||||
|  |             ipVO.setDisplay(displayIp); | ||||||
|  |         } | ||||||
|  |         ipVO = _ipAddressDao.persist(ipVO); | ||||||
|  |         if (ipDedicatedAccountId == null) { | ||||||
|  |             _resourceLimitMgr.incrementResourceCount(account.getId(), Resource.ResourceType.public_ip); | ||||||
|  |         } | ||||||
|  |         return ipVO; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Long getIpDedicatedAccountId(Long vlanId) { | ||||||
|  |         List<AccountVlanMapVO> accountVlanMaps = _accountVlanMapDao.listAccountVlanMapsByVlan(vlanId); | ||||||
|  |         if (CollectionUtils.isNotEmpty(accountVlanMaps)) { | ||||||
|  |             return accountVlanMaps.get(0).getAccountId(); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Long getIpDedicatedDomainId(Long vlanId) { | ||||||
|  |         List<DomainVlanMapVO> domainVlanMaps = _domainVlanMapDao.listDomainVlanMapsByVlan(vlanId); | ||||||
|  |         if (CollectionUtils.isNotEmpty(domainVlanMaps)) { | ||||||
|  |             return domainVlanMaps.get(0).getDomainId(); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     @ActionEvent(eventType = EventTypes.EVENT_NET_IP_RELEASE, eventDescription = "releasing Reserved Ip", async = false) | ||||||
|  |     public boolean releaseReservedIpAddress(long ipAddressId) throws InsufficientAddressCapacityException { | ||||||
|  |         IPAddressVO ipVO = _ipAddressDao.findById(ipAddressId); | ||||||
|  |         if (ipVO == null) { | ||||||
|  |             throw new InvalidParameterValueException("Unable to find IP address by ID=" + ipAddressId); | ||||||
|  |         } | ||||||
|  |         if (ipVO.isPortable()) { | ||||||
|  |             throw new InvalidParameterValueException("Unable to release a portable IP, please use disassociateIpAddress instead"); | ||||||
|  |         } | ||||||
|  |         if (State.Allocated.equals(ipVO.getState())) { | ||||||
|  |             throw new InvalidParameterValueException("Unable to release a public IP in Allocated state, please use disassociateIpAddress instead"); | ||||||
|  |         } | ||||||
|  |         return releaseIpAddressInternal(ipAddressId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     @ActionEvent(eventType = EventTypes.EVENT_NET_IP_RELEASE, eventDescription = "disassociating Ip", async = true) |     @ActionEvent(eventType = EventTypes.EVENT_NET_IP_RELEASE, eventDescription = "disassociating Ip", async = true) | ||||||
|     public boolean releaseIpAddress(long ipAddressId) throws InsufficientAddressCapacityException { |     public boolean releaseIpAddress(long ipAddressId) throws InsufficientAddressCapacityException { | ||||||
| @ -961,6 +1064,15 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C | |||||||
|             throwInvalidIdException("Can't release system IP address with specified id", ipVO.getUuid(), "systemIpAddrId"); |             throwInvalidIdException("Can't release system IP address with specified id", ipVO.getUuid(), "systemIpAddrId"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         if (State.Reserved.equals(ipVO.getState())) { | ||||||
|  |             _ipAddressDao.unassignIpAddress(ipVO.getId()); | ||||||
|  |             Long ipDedicatedAccountId = getIpDedicatedAccountId(ipVO.getVlanId()); | ||||||
|  |             if (ipDedicatedAccountId == null) { | ||||||
|  |                 _resourceLimitMgr.decrementResourceCount(ipVO.getAccountId(), Resource.ResourceType.public_ip); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         boolean success = _ipAddrMgr.disassociatePublicIpAddress(ipAddressId, userId, caller); |         boolean success = _ipAddrMgr.disassociatePublicIpAddress(ipAddressId, userId, caller); | ||||||
| 
 | 
 | ||||||
|         if (success) { |         if (success) { | ||||||
|  | |||||||
| @ -316,6 +316,8 @@ import org.apache.cloudstack.api.command.user.account.ListProjectAccountsCmd; | |||||||
| import org.apache.cloudstack.api.command.user.address.AssociateIPAddrCmd; | import org.apache.cloudstack.api.command.user.address.AssociateIPAddrCmd; | ||||||
| import org.apache.cloudstack.api.command.user.address.DisassociateIPAddrCmd; | import org.apache.cloudstack.api.command.user.address.DisassociateIPAddrCmd; | ||||||
| import org.apache.cloudstack.api.command.user.address.ListPublicIpAddressesCmd; | import org.apache.cloudstack.api.command.user.address.ListPublicIpAddressesCmd; | ||||||
|  | import org.apache.cloudstack.api.command.user.address.ReleaseIPAddrCmd; | ||||||
|  | import org.apache.cloudstack.api.command.user.address.ReserveIPAddrCmd; | ||||||
| import org.apache.cloudstack.api.command.user.address.UpdateIPAddrCmd; | import org.apache.cloudstack.api.command.user.address.UpdateIPAddrCmd; | ||||||
| import org.apache.cloudstack.api.command.user.affinitygroup.CreateAffinityGroupCmd; | import org.apache.cloudstack.api.command.user.affinitygroup.CreateAffinityGroupCmd; | ||||||
| import org.apache.cloudstack.api.command.user.affinitygroup.DeleteAffinityGroupCmd; | import org.apache.cloudstack.api.command.user.affinitygroup.DeleteAffinityGroupCmd; | ||||||
| @ -2147,13 +2149,13 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe | |||||||
|         final String state = cmd.getState(); |         final String state = cmd.getState(); | ||||||
|         Boolean isAllocated = cmd.isAllocatedOnly(); |         Boolean isAllocated = cmd.isAllocatedOnly(); | ||||||
|         if (isAllocated == null) { |         if (isAllocated == null) { | ||||||
|             if (state != null && state.equalsIgnoreCase(IpAddress.State.Free.name())) { |             if (state != null && (state.equalsIgnoreCase(IpAddress.State.Free.name()) || state.equalsIgnoreCase(IpAddress.State.Reserved.name()))) { | ||||||
|                 isAllocated = Boolean.FALSE; |                 isAllocated = Boolean.FALSE; | ||||||
|             } else { |             } else { | ||||||
|                 isAllocated = Boolean.TRUE; // default |                 isAllocated = Boolean.TRUE; // default | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             if (state != null && state.equalsIgnoreCase(IpAddress.State.Free.name())) { |             if (state != null && (state.equalsIgnoreCase(IpAddress.State.Free.name()) || state.equalsIgnoreCase(IpAddress.State.Reserved.name()))) { | ||||||
|                 if (isAllocated) { |                 if (isAllocated) { | ||||||
|                     throw new InvalidParameterValueException("Conflict: allocatedonly is true but state is Free"); |                     throw new InvalidParameterValueException("Conflict: allocatedonly is true but state is Free"); | ||||||
|                 } |                 } | ||||||
| @ -2242,7 +2244,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe | |||||||
|         buildParameters(sb, cmd, vlanType == VlanType.VirtualNetwork ? true : isAllocated); |         buildParameters(sb, cmd, vlanType == VlanType.VirtualNetwork ? true : isAllocated); | ||||||
| 
 | 
 | ||||||
|         SearchCriteria<IPAddressVO> sc = sb.create(); |         SearchCriteria<IPAddressVO> sc = sb.create(); | ||||||
|         setParameters(sc, cmd, vlanType); |         setParameters(sc, cmd, vlanType, isAllocated); | ||||||
| 
 | 
 | ||||||
|         if (isAllocated || (vlanType == VlanType.VirtualNetwork && (caller.getType() != Account.Type.ADMIN || cmd.getDomainId() != null))) { |         if (isAllocated || (vlanType == VlanType.VirtualNetwork && (caller.getType() != Account.Type.ADMIN || cmd.getDomainId() != null))) { | ||||||
|             _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); |             _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); | ||||||
| @ -2305,7 +2307,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe | |||||||
|             sb2.and("ids", sb2.entity().getId(), SearchCriteria.Op.IN); |             sb2.and("ids", sb2.entity().getId(), SearchCriteria.Op.IN); | ||||||
| 
 | 
 | ||||||
|             SearchCriteria<IPAddressVO> sc2 = sb2.create(); |             SearchCriteria<IPAddressVO> sc2 = sb2.create(); | ||||||
|             setParameters(sc2, cmd, vlanType); |             setParameters(sc2, cmd, vlanType, isAllocated); | ||||||
|             sc2.setParameters("ids", freeAddrIds.toArray()); |             sc2.setParameters("ids", freeAddrIds.toArray()); | ||||||
|             addrs.addAll(_publicIpAddressDao.search(sc2, searchFilter)); // Allocated + Free |             addrs.addAll(_publicIpAddressDao.search(sc2, searchFilter)); // Allocated + Free | ||||||
|         } |         } | ||||||
| @ -2369,7 +2371,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void setParameters(SearchCriteria<IPAddressVO> sc, final ListPublicIpAddressesCmd cmd, VlanType vlanType) { |     private void setParameters(SearchCriteria<IPAddressVO> sc, final ListPublicIpAddressesCmd cmd, VlanType vlanType, Boolean isAllocated) { | ||||||
|         final Object keyword = cmd.getKeyword(); |         final Object keyword = cmd.getKeyword(); | ||||||
|         final Long physicalNetworkId = cmd.getPhysicalNetworkId(); |         final Long physicalNetworkId = cmd.getPhysicalNetworkId(); | ||||||
|         final Long sourceNetworkId = cmd.getNetworkId(); |         final Long sourceNetworkId = cmd.getNetworkId(); | ||||||
| @ -2437,6 +2439,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe | |||||||
| 
 | 
 | ||||||
|         if (state != null) { |         if (state != null) { | ||||||
|             sc.setParameters("state", state); |             sc.setParameters("state", state); | ||||||
|  |         } else if (isAllocated != null && isAllocated) { | ||||||
|  |             sc.setParameters("state", IpAddress.State.Allocated); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         sc.setParameters( "forsystemvms", false); |         sc.setParameters( "forsystemvms", false); | ||||||
| @ -3199,6 +3203,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe | |||||||
|         cmdList.add(ListProjectAccountsCmd.class); |         cmdList.add(ListProjectAccountsCmd.class); | ||||||
|         cmdList.add(AssociateIPAddrCmd.class); |         cmdList.add(AssociateIPAddrCmd.class); | ||||||
|         cmdList.add(DisassociateIPAddrCmd.class); |         cmdList.add(DisassociateIPAddrCmd.class); | ||||||
|  |         cmdList.add(ReserveIPAddrCmd.class); | ||||||
|  |         cmdList.add(ReleaseIPAddrCmd.class); | ||||||
|         cmdList.add(ListPublicIpAddressesCmd.class); |         cmdList.add(ListPublicIpAddressesCmd.class); | ||||||
|         cmdList.add(CreateAutoScalePolicyCmd.class); |         cmdList.add(CreateAutoScalePolicyCmd.class); | ||||||
|         cmdList.add(CreateAutoScaleVmGroupCmd.class); |         cmdList.add(CreateAutoScaleVmGroupCmd.class); | ||||||
|  | |||||||
| @ -166,6 +166,16 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches | |||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public IpAddress reserveIpAddress(Account account, Boolean displayIp, Long ipAddressId) throws ResourceAllocationException { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean releaseReservedIpAddress(long ipAddressId) throws InsufficientAddressCapacityException { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long networkId, Long vpcId) throws ResourceAllocationException, |     public IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long networkId, Long vpcId) throws ResourceAllocationException, | ||||||
|         InsufficientAddressCapacityException, ConcurrentOperationException { |         InsufficientAddressCapacityException, ConcurrentOperationException { | ||||||
|  | |||||||
| @ -248,6 +248,7 @@ | |||||||
| "label.action.remove.host": "Remove Host", | "label.action.remove.host": "Remove Host", | ||||||
| "label.action.remove.host.processing": "Removing Host....", | "label.action.remove.host.processing": "Removing Host....", | ||||||
| "label.action.remove.vm": "Release VM", | "label.action.remove.vm": "Release VM", | ||||||
|  | "label.action.reserve.ip": "Reserve Public IP", | ||||||
| "label.action.reset.password": "Reset Password", | "label.action.reset.password": "Reset Password", | ||||||
| "label.action.reset.password.processing": "Resetting Password....", | "label.action.reset.password.processing": "Resetting Password....", | ||||||
| "label.action.resize.volume": "Resize Volume", | "label.action.resize.volume": "Resize Volume", | ||||||
| @ -975,6 +976,7 @@ | |||||||
| "label.forceencap": "Force UDP Encapsulation of ESP Packets", | "label.forceencap": "Force UDP Encapsulation of ESP Packets", | ||||||
| "label.forgedtransmits": "Forged Transmits", | "label.forgedtransmits": "Forged Transmits", | ||||||
| "label.format": "Format", | "label.format": "Format", | ||||||
|  | "label.free": "Free", | ||||||
| "label.french.azerty.keyboard": "French AZERTY keyboard", | "label.french.azerty.keyboard": "French AZERTY keyboard", | ||||||
| "label.friday": "Friday", | "label.friday": "Friday", | ||||||
| "label.from": "from", | "label.from": "from", | ||||||
| @ -1874,6 +1876,7 @@ | |||||||
| "label.required": "Required", | "label.required": "Required", | ||||||
| "label.requireshvm": "HVM", | "label.requireshvm": "HVM", | ||||||
| "label.requiresupgrade": "Requires Upgrade", | "label.requiresupgrade": "Requires Upgrade", | ||||||
|  | "label.reserved": "Reserved", | ||||||
| "label.reserved.system.gateway": "Reserved system gateway", | "label.reserved.system.gateway": "Reserved system gateway", | ||||||
| "label.reserved.system.ip": "Reserved System IP", | "label.reserved.system.ip": "Reserved System IP", | ||||||
| "label.reserved.system.netmask": "Reserved system netmask", | "label.reserved.system.netmask": "Reserved system netmask", | ||||||
| @ -2573,6 +2576,7 @@ | |||||||
| "message.action.recover.volume": "Please confirm that you would like to recover this volume.", | "message.action.recover.volume": "Please confirm that you would like to recover this volume.", | ||||||
| "message.action.release.ip": "Please confirm that you want to release this IP.", | "message.action.release.ip": "Please confirm that you want to release this IP.", | ||||||
| "message.action.remove.host": "Please confirm that you want to remove this host.", | "message.action.remove.host": "Please confirm that you want to remove this host.", | ||||||
|  | "message.action.reserve.ip": "Please confirm that you want to reserve this IP.", | ||||||
| "message.action.reset.password.off": "Your instance currently does not support this feature.", | "message.action.reset.password.off": "Your instance currently does not support this feature.", | ||||||
| "message.action.reset.password.warning": "Your instance must be stopped before attempting to change its current password.", | "message.action.reset.password.warning": "Your instance must be stopped before attempting to change its current password.", | ||||||
| "message.action.restore.instance": "Please confirm that you want to restore this instance.", | "message.action.restore.instance": "Please confirm that you want to restore this instance.", | ||||||
| @ -3204,6 +3208,7 @@ | |||||||
| "message.publicip.state.allocating": "The IP Address is being propagated to other network elements and is not ready for use yet.", | "message.publicip.state.allocating": "The IP Address is being propagated to other network elements and is not ready for use yet.", | ||||||
| "message.publicip.state.free": "The IP address is ready to be allocated.", | "message.publicip.state.free": "The IP address is ready to be allocated.", | ||||||
| "message.publicip.state.releasing": "The IP address is being released for other network elements and is not ready for allocation.", | "message.publicip.state.releasing": "The IP address is being released for other network elements and is not ready for allocation.", | ||||||
|  | "message.publicip.state.reserved": "The IP address is reserved and not ready for use.", | ||||||
| "message.question.are.you.sure.you.want.to.add": "Are you sure you want to add", | "message.question.are.you.sure.you.want.to.add": "Are you sure you want to add", | ||||||
| "message.read.accept.license.agreements": "Please read and accept the terms for the license agreements.", | "message.read.accept.license.agreements": "Please read and accept the terms for the license agreements.", | ||||||
| "message.read.admin.guide.scaling.up": "Please read the dynamic scaling section in the admin guide before scaling up.", | "message.read.admin.guide.scaling.up": "Please read the dynamic scaling section in the admin guide before scaling up.", | ||||||
|  | |||||||
| @ -156,6 +156,8 @@ export default { | |||||||
|       switch (state.toLowerCase()) { |       switch (state.toLowerCase()) { | ||||||
|         case 'scheduled': |         case 'scheduled': | ||||||
|           return 'blue' |           return 'blue' | ||||||
|  |         case 'reserved': | ||||||
|  |           return 'orange' | ||||||
|         default: |         default: | ||||||
|           return null |           return null | ||||||
|       } |       } | ||||||
|  | |||||||
| @ -275,6 +275,7 @@ export default { | |||||||
|       resourceType: 'PublicIpAddress', |       resourceType: 'PublicIpAddress', | ||||||
|       columns: ['ipaddress', 'state', 'associatednetworkname', 'virtualmachinename', 'allocated', 'account', 'zonename'], |       columns: ['ipaddress', 'state', 'associatednetworkname', 'virtualmachinename', 'allocated', 'account', 'zonename'], | ||||||
|       details: ['ipaddress', 'id', 'associatednetworkname', 'virtualmachinename', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'zonename'], |       details: ['ipaddress', 'id', 'associatednetworkname', 'virtualmachinename', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'zonename'], | ||||||
|  |       filters: ['allocated', 'reserved', 'free'], | ||||||
|       component: shallowRef(() => import('@/views/network/PublicIpResource.vue')), |       component: shallowRef(() => import('@/views/network/PublicIpResource.vue')), | ||||||
|       tabs: [{ |       tabs: [{ | ||||||
|         name: 'details', |         name: 'details', | ||||||
| @ -311,7 +312,7 @@ export default { | |||||||
|           label: 'label.action.enable.static.nat', |           label: 'label.action.enable.static.nat', | ||||||
|           docHelp: 'adminguide/networking_and_traffic.html#enabling-or-disabling-static-nat', |           docHelp: 'adminguide/networking_and_traffic.html#enabling-or-disabling-static-nat', | ||||||
|           dataView: true, |           dataView: true, | ||||||
|           show: (record) => { return !record.virtualmachineid && !record.issourcenat }, |           show: (record) => { return record.state === 'Allocated' && !record.virtualmachineid && !record.issourcenat }, | ||||||
|           popup: true, |           popup: true, | ||||||
|           component: shallowRef(defineAsyncComponent(() => import('@/views/network/EnableStaticNat.vue'))) |           component: shallowRef(defineAsyncComponent(() => import('@/views/network/EnableStaticNat.vue'))) | ||||||
|         }, |         }, | ||||||
| @ -337,7 +338,27 @@ export default { | |||||||
|           message: 'message.action.release.ip', |           message: 'message.action.release.ip', | ||||||
|           docHelp: 'adminguide/networking_and_traffic.html#releasing-an-ip-address-alloted-to-a-vpc', |           docHelp: 'adminguide/networking_and_traffic.html#releasing-an-ip-address-alloted-to-a-vpc', | ||||||
|           dataView: true, |           dataView: true, | ||||||
|           show: (record) => { return !record.issourcenat }, |           show: (record) => { return record.state === 'Allocated' && !record.issourcenat }, | ||||||
|  |           groupAction: true, | ||||||
|  |           popup: true, | ||||||
|  |           groupMap: (selection) => { return selection.map(x => { return { id: x } }) } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           api: 'reserveIpAddress', | ||||||
|  |           icon: 'lock-outlined', | ||||||
|  |           label: 'label.action.reserve.ip', | ||||||
|  |           dataView: true, | ||||||
|  |           show: (record) => { return record.state === 'Free' }, | ||||||
|  |           popup: true, | ||||||
|  |           component: shallowRef(defineAsyncComponent(() => import('@/views/network/ReservePublicIP.vue'))) | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           api: 'releaseIpAddress', | ||||||
|  |           icon: 'delete-outlined', | ||||||
|  |           label: 'label.action.release.ip', | ||||||
|  |           message: 'message.action.release.ip', | ||||||
|  |           dataView: true, | ||||||
|  |           show: (record) => { return record.state === 'Reserved' }, | ||||||
|           groupAction: true, |           groupAction: true, | ||||||
|           popup: true, |           popup: true, | ||||||
|           groupMap: (selection) => { return selection.map(x => { return { id: x } }) } |           groupMap: (selection) => { return selection.map(x => { return { id: x } }) } | ||||||
|  | |||||||
| @ -55,7 +55,8 @@ | |||||||
|                     :placeholder="$t('label.filterby')" |                     :placeholder="$t('label.filterby')" | ||||||
|                     :value="$route.query.filter || (projectView && $route.name === 'vm' || |                     :value="$route.query.filter || (projectView && $route.name === 'vm' || | ||||||
|                       ['Admin', 'DomainAdmin'].includes($store.getters.userInfo.roletype) && ['vm', 'iso', 'template'].includes($route.name) |                       ['Admin', 'DomainAdmin'].includes($store.getters.userInfo.roletype) && ['vm', 'iso', 'template'].includes($route.name) | ||||||
|                       ? 'all' : ['guestnetwork'].includes($route.name) ? 'all' : 'self')" |                       ? 'all' : ['publicip'].includes($route.name) | ||||||
|  |                         ? 'allocated': ['guestnetwork'].includes($route.name) ? 'all' : 'self')" | ||||||
|                     style="min-width: 100px; margin-left: 10px" |                     style="min-width: 100px; margin-left: 10px" | ||||||
|                     @change="changeFilter" |                     @change="changeFilter" | ||||||
|                     showSearch |                     showSearch | ||||||
| @ -851,6 +852,9 @@ export default { | |||||||
|           delete params.id |           delete params.id | ||||||
|           params.name = this.$route.params.id |           params.name = this.$route.params.id | ||||||
|         } |         } | ||||||
|  |         if (['listPublicIpAddresses'].includes(this.apiName)) { | ||||||
|  |           params.allocatedonly = false | ||||||
|  |         } | ||||||
|         if (this.$route.path.startsWith('/vmsnapshot/')) { |         if (this.$route.path.startsWith('/vmsnapshot/')) { | ||||||
|           params.vmsnapshotid = this.$route.params.id |           params.vmsnapshotid = this.$route.params.id | ||||||
|         } else if (this.$route.path.startsWith('/ldapsetting/')) { |         } else if (this.$route.path.startsWith('/ldapsetting/')) { | ||||||
| @ -1494,6 +1498,8 @@ export default { | |||||||
|         } else { |         } else { | ||||||
|           query.type = filter |           query.type = filter | ||||||
|         } |         } | ||||||
|  |       } else if (this.$route.name === 'publicip') { | ||||||
|  |         query.state = filter | ||||||
|       } else if (this.$route.name === 'vm') { |       } else if (this.$route.name === 'vm') { | ||||||
|         if (filter === 'self') { |         if (filter === 'self') { | ||||||
|           query.account = this.$store.getters.userInfo.account |           query.account = this.$store.getters.userInfo.account | ||||||
|  | |||||||
| @ -137,7 +137,7 @@ | |||||||
|               }" > |               }" > | ||||||
|               <a-select-option |               <a-select-option | ||||||
|                 v-for="ip in listPublicIpAddress" |                 v-for="ip in listPublicIpAddress" | ||||||
|                 :key="ip.ipaddress">{{ ip.ipaddress }}</a-select-option> |                 :key="ip.ipaddress">{{ ip.ipaddress }} ({{ ip.state }})</a-select-option> | ||||||
|             </a-select> |             </a-select> | ||||||
|           </a-form-item> |           </a-form-item> | ||||||
|           <div :span="24" class="action-button"> |           <div :span="24" class="action-button"> | ||||||
| @ -367,7 +367,7 @@ export default { | |||||||
|       }).catch(error => { |       }).catch(error => { | ||||||
|         this.$notification.error({ |         this.$notification.error({ | ||||||
|           message: `${this.$t('label.error')} ${error.response.status}`, |           message: `${this.$t('label.error')} ${error.response.status}`, | ||||||
|           description: error.response.data.errorresponse.errortext, |           description: error.response.data.associateipaddressresponse.errortext || error.response.data.errorresponse.errortext, | ||||||
|           duration: 0 |           duration: 0 | ||||||
|         }) |         }) | ||||||
|       }).finally(() => { |       }).finally(() => { | ||||||
| @ -447,9 +447,10 @@ export default { | |||||||
|       try { |       try { | ||||||
|         const listPublicIpAddress = await this.fetchListPublicIpAddress() |         const listPublicIpAddress = await this.fetchListPublicIpAddress() | ||||||
|         listPublicIpAddress.forEach(item => { |         listPublicIpAddress.forEach(item => { | ||||||
|           if (item.state === 'Free') { |           if (item.state === 'Free' || item.state === 'Reserved') { | ||||||
|             this.listPublicIpAddress.push({ |             this.listPublicIpAddress.push({ | ||||||
|               ipaddress: item.ipaddress |               ipaddress: item.ipaddress, | ||||||
|  |               state: item.state | ||||||
|             }) |             }) | ||||||
|           } |           } | ||||||
|         }) |         }) | ||||||
|  | |||||||
| @ -113,6 +113,11 @@ export default { | |||||||
|       this.loading = false |       this.loading = false | ||||||
|     }, |     }, | ||||||
|     async filterTabs () { |     async filterTabs () { | ||||||
|  |       // Public IPs in Free state have nothing | ||||||
|  |       if (['Free', 'Reserved'].includes(this.resource.state)) { | ||||||
|  |         this.tabs = this.defaultTabs | ||||||
|  |         return | ||||||
|  |       } | ||||||
|       // VPC IPs with source nat have only VPN |       // VPC IPs with source nat have only VPN | ||||||
|       if (this.resource && this.resource.vpcid && this.resource.issourcenat) { |       if (this.resource && this.resource.vpcid && this.resource.issourcenat) { | ||||||
|         this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) |         this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn')) | ||||||
|  | |||||||
							
								
								
									
										332
									
								
								ui/src/views/network/ReservePublicIP.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										332
									
								
								ui/src/views/network/ReservePublicIP.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,332 @@ | |||||||
|  | // 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. | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <div class="form" v-ctrl-enter="submitData"> | ||||||
|  | 
 | ||||||
|  |       <div v-if="loading" class="loading"> | ||||||
|  |         <loading-outlined style="color: #1890ff;" /> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <a-alert type="warning" style="margin-bottom: 20px"> | ||||||
|  |         <template #message> | ||||||
|  |           <label v-html="$t('message.action.reserve.ip')"></label> | ||||||
|  |         </template> | ||||||
|  |       </a-alert> | ||||||
|  | 
 | ||||||
|  |       <div class="form__item"> | ||||||
|  |         <p class="form__label">{{ $t('label.accounttype') }}</p> | ||||||
|  |         <a-select | ||||||
|  |           v-model:value="selectedAccountType" | ||||||
|  |           v-focus="true" | ||||||
|  |           showSearch | ||||||
|  |           optionFilterProp="label" | ||||||
|  |           :filterOption="(input, option) => { | ||||||
|  |             return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 | ||||||
|  |           }"> | ||||||
|  |           <a-select-option :value="$t('label.account')">{{ $t('label.account') }}</a-select-option> | ||||||
|  |           <a-select-option :value="$t('label.project')">{{ $t('label.project') }}</a-select-option> | ||||||
|  |         </a-select> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <div class="form__item" v-if="isAdminOrDomainAdmin()" > | ||||||
|  |         <p class="form__label"><span class="required">*</span>{{ $t('label.domain') }}</p> | ||||||
|  |         <a-select | ||||||
|  |           @change="changeDomain" | ||||||
|  |           v-model:value="selectedDomain" | ||||||
|  |           showSearch | ||||||
|  |           optionFilterProp="label" | ||||||
|  |           :filterOption="(input, option) => { | ||||||
|  |             return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 | ||||||
|  |           }" > | ||||||
|  |           <a-select-option v-for="domain in domains" :key="domain.name" :value="domain.id" :label="domain.path || domain.name || domain.description"> | ||||||
|  |             <span> | ||||||
|  |               <resource-icon v-if="domain && domain.icon" :image="domain.icon.base64image" size="1x" style="margin-right: 5px"/> | ||||||
|  |               <block-outlined v-else style="margin-right: 5px" /> | ||||||
|  |               {{ domain.path || domain.name || domain.description }} | ||||||
|  |             </span> | ||||||
|  |           </a-select-option> | ||||||
|  |         </a-select> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |       <template v-if="selectedAccountType === $t('label.account')"> | ||||||
|  |         <div class="form__item" v-if="isAdminOrDomainAdmin()"> | ||||||
|  |           <p class="form__label"><span class="required">*</span>{{ $t('label.account') }}</p> | ||||||
|  |           <a-select | ||||||
|  |             @change="changeAccount" | ||||||
|  |             v-model:value="selectedAccount" | ||||||
|  |             showSearch | ||||||
|  |             optionFilterProp="label" | ||||||
|  |             :filterOption="(input, option) => { | ||||||
|  |               return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0 | ||||||
|  |             }" > | ||||||
|  |             <a-select-option v-for="account in accounts" :key="account.name" :value="account.name"> | ||||||
|  |               <span> | ||||||
|  |                 <resource-icon v-if="account && account.icon" :image="account.icon.base64image" size="1x" style="margin-right: 5px"/> | ||||||
|  |                 <team-outlined v-else style="margin-right: 5px" /> | ||||||
|  |                 {{ account.name }} | ||||||
|  |               </span> | ||||||
|  |             </a-select-option> | ||||||
|  |           </a-select> | ||||||
|  |           <span v-if="accountError" class="required">{{ $t('label.required') }}</span> | ||||||
|  |         </div> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <template v-else> | ||||||
|  |         <div class="form__item"> | ||||||
|  |           <p class="form__label"><span class="required">*</span>{{ $t('label.project') }}</p> | ||||||
|  |           <a-select | ||||||
|  |             @change="changeProject" | ||||||
|  |             v-model:value="selectedProject" | ||||||
|  |             showSearch | ||||||
|  |             optionFilterProp="label" | ||||||
|  |             :filterOption="(input, option) => { | ||||||
|  |               return  option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 | ||||||
|  |             }" > | ||||||
|  |             <a-select-option v-for="project in projects" :key="project.id" :value="project.id" :label="project.name"> | ||||||
|  |               <span> | ||||||
|  |                 <resource-icon v-if="project && project.icon" :image="project.icon.base64image" size="1x" style="margin-right: 5px"/> | ||||||
|  |                 <project-outlined v-else style="margin-right: 5px" /> | ||||||
|  |                 {{ project.name }} | ||||||
|  |               </span> | ||||||
|  |             </a-select-option> | ||||||
|  |           </a-select> | ||||||
|  |           <span v-if="projectError" class="required">{{ $t('label.required') }}</span> | ||||||
|  |         </div> | ||||||
|  |       </template> | ||||||
|  | 
 | ||||||
|  |       <div class="submit-btn"> | ||||||
|  |         <a-button @click="closeAction"> | ||||||
|  |           {{ $t('label.cancel') }} | ||||||
|  |         </a-button> | ||||||
|  |         <a-button type="primary" @click="submitData" ref="submit"> | ||||||
|  |           {{ $t('label.submit') }} | ||||||
|  |         </a-button> | ||||||
|  |       </div> | ||||||
|  | 
 | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script> | ||||||
|  | import { api } from '@/api' | ||||||
|  | import ResourceIcon from '@/components/view/ResourceIcon' | ||||||
|  | import { isAdminOrDomainAdmin } from '@/role' | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   name: 'AssignInstance', | ||||||
|  |   props: { | ||||||
|  |     resource: { | ||||||
|  |       type: Object, | ||||||
|  |       required: true | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   components: { | ||||||
|  |     ResourceIcon | ||||||
|  |   }, | ||||||
|  |   inject: ['parentFetchData'], | ||||||
|  |   data () { | ||||||
|  |     return { | ||||||
|  |       domains: [], | ||||||
|  |       accounts: [], | ||||||
|  |       projects: [], | ||||||
|  |       selectedAccountType: 'Account', | ||||||
|  |       selectedDomain: null, | ||||||
|  |       selectedAccount: null, | ||||||
|  |       selectedProject: null, | ||||||
|  |       accountError: false, | ||||||
|  |       projectError: false, | ||||||
|  |       loading: false | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   created () { | ||||||
|  |     if (isAdminOrDomainAdmin()) { | ||||||
|  |       this.fetchData() | ||||||
|  |     } else { | ||||||
|  |       this.fetchProjects() | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     isAdminOrDomainAdmin () { | ||||||
|  |       return isAdminOrDomainAdmin() | ||||||
|  |     }, | ||||||
|  |     fetchData () { | ||||||
|  |       this.loading = true | ||||||
|  |       api('listDomains', { | ||||||
|  |         response: 'json', | ||||||
|  |         listAll: true, | ||||||
|  |         showicon: true, | ||||||
|  |         details: 'min' | ||||||
|  |       }).then(response => { | ||||||
|  |         this.domains = response.listdomainsresponse.domain || [] | ||||||
|  |         this.selectedDomain = this.domains[0].id | ||||||
|  |         this.fetchAccounts() | ||||||
|  |         this.fetchProjects() | ||||||
|  |       }).catch(error => { | ||||||
|  |         this.$notifyError(error) | ||||||
|  |       }).finally(() => { | ||||||
|  |         this.loading = false | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     fetchAccounts () { | ||||||
|  |       this.loading = true | ||||||
|  |       api('listAccounts', { | ||||||
|  |         response: 'json', | ||||||
|  |         domainId: this.selectedDomain, | ||||||
|  |         showicon: true, | ||||||
|  |         state: 'Enabled', | ||||||
|  |         isrecursive: false | ||||||
|  |       }).then(response => { | ||||||
|  |         this.accounts = response.listaccountsresponse.account || [] | ||||||
|  |       }).catch(error => { | ||||||
|  |         this.$notifyError(error) | ||||||
|  |       }).finally(() => { | ||||||
|  |         this.loading = false | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     fetchProjects () { | ||||||
|  |       this.loading = true | ||||||
|  |       api('listProjects', { | ||||||
|  |         response: 'json', | ||||||
|  |         domainId: this.selectedDomain, | ||||||
|  |         state: 'Active', | ||||||
|  |         showicon: true, | ||||||
|  |         details: 'min', | ||||||
|  |         isrecursive: false | ||||||
|  |       }).then(response => { | ||||||
|  |         this.projects = response.listprojectsresponse.project || [] | ||||||
|  |       }).catch(error => { | ||||||
|  |         this.$notifyError(error) | ||||||
|  |       }).finally(() => { | ||||||
|  |         this.loading = false | ||||||
|  |       }) | ||||||
|  |     }, | ||||||
|  |     changeDomain () { | ||||||
|  |       this.selectedAccount = null | ||||||
|  |       this.selectedProject = null | ||||||
|  |       this.fetchAccounts() | ||||||
|  |       this.fetchProjects() | ||||||
|  |     }, | ||||||
|  |     changeAccount () { | ||||||
|  |       this.selectedProject = null | ||||||
|  |     }, | ||||||
|  |     changeProject () { | ||||||
|  |       this.selectedAccount = null | ||||||
|  |     }, | ||||||
|  |     closeAction () { | ||||||
|  |       this.$emit('close-action') | ||||||
|  |     }, | ||||||
|  |     submitData () { | ||||||
|  |       if (this.loading) return | ||||||
|  | 
 | ||||||
|  |       let variableKey = '' | ||||||
|  |       let variableValue = '' | ||||||
|  | 
 | ||||||
|  |       if (this.selectedAccountType === 'Account') { | ||||||
|  |         if (!this.selectedAccount && isAdminOrDomainAdmin()) { | ||||||
|  |           this.accountError = true | ||||||
|  |           return | ||||||
|  |         } | ||||||
|  |         variableKey = 'account' | ||||||
|  |         variableValue = this.selectedAccount | ||||||
|  |       } else if (this.selectedAccountType === 'Project') { | ||||||
|  |         if (!this.selectedProject) { | ||||||
|  |           this.projectError = true | ||||||
|  |           return | ||||||
|  |         } | ||||||
|  |         variableKey = 'projectid' | ||||||
|  |         variableValue = this.selectedProject | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this.loading = true | ||||||
|  |       api('reserveIpAddress', { | ||||||
|  |         response: 'json', | ||||||
|  |         id: this.resource.id, | ||||||
|  |         domainid: this.selectedDomain, | ||||||
|  |         [variableKey]: variableValue | ||||||
|  |       }).then(response => { | ||||||
|  |         this.$notification.success({ | ||||||
|  |           message: this.$t('label.action.reserve.ip') | ||||||
|  |         }) | ||||||
|  |         this.closeAction() | ||||||
|  |         this.$emit('refresh-data') | ||||||
|  |       }).catch(error => { | ||||||
|  |         this.$notifyError(error) | ||||||
|  |       }).finally(() => { | ||||||
|  |         this.loading = false | ||||||
|  |       }) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style scoped lang="scss"> | ||||||
|  |   .form { | ||||||
|  |     width: 85vw; | ||||||
|  | 
 | ||||||
|  |     @media (min-width: 760px) { | ||||||
|  |       width: 500px; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|  | 
 | ||||||
|  |     &__item { | ||||||
|  |       display: flex; | ||||||
|  |       flex-direction: column; | ||||||
|  |       width: 100%; | ||||||
|  |       margin-bottom: 10px; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &__label { | ||||||
|  |       display: flex; | ||||||
|  |       font-weight: bold; | ||||||
|  |       margin-bottom: 5px; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .submit-btn { | ||||||
|  |     margin-top: 10px; | ||||||
|  |     align-self: flex-end; | ||||||
|  | 
 | ||||||
|  |     button { | ||||||
|  |       margin-left: 10px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .required { | ||||||
|  |     margin-right: 2px; | ||||||
|  |     color: red; | ||||||
|  |     font-size: 0.7rem; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .loading { | ||||||
|  |     position: absolute; | ||||||
|  |     top: 0; | ||||||
|  |     right: 0; | ||||||
|  |     bottom: 0; | ||||||
|  |     left: 0; | ||||||
|  |     z-index: 1; | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     justify-content: center; | ||||||
|  |     font-size: 3rem; | ||||||
|  |   } | ||||||
|  | </style> | ||||||
| @ -12,6 +12,7 @@ | |||||||
|       "state.error": "Error", |       "state.error": "Error", | ||||||
|       "message.publicip.state.allocated": "Allocated", |       "message.publicip.state.allocated": "Allocated", | ||||||
|       "message.publicip.state.created": "Created", |       "message.publicip.state.created": "Created", | ||||||
|  |       "message.publicip.state.reserved": "Reserved", | ||||||
|       "message.vmsnapshot.state.active": "Active", |       "message.vmsnapshot.state.active": "Active", | ||||||
|       "message.vm.state.active": "Active", |       "message.vm.state.active": "Active", | ||||||
|       "message.volume.state.active": "Active", |       "message.volume.state.active": "Active", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user