Skip to content

Commit 16d45f7

Browse files
authored
Save the account which deliberately removed a public IP from quarantine (#8339)
When a public IP gets removed from quarantine, the removal reason gets saved to the database; however, it may also be useful for operators to know who removed the public IP from quarantine. For that reason, this PR extends the public IP quarantine feature so that the account that deliberately removed an IP from quarantine also gets saved to the database.
1 parent af87222 commit 16d45f7

10 files changed

Lines changed: 123 additions & 0 deletions

File tree

api/src/main/java/com/cloud/network/PublicIpQuarantine.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public interface PublicIpQuarantine extends InternalIdentity, Identity {
3030

3131
String getRemovalReason();
3232

33+
Long getRemoverAccountId();
34+
3335
Date getRemoved();
3436

3537
Date getCreated();

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,7 @@ public class ApiConstants {
803803
public static final String IPSEC_PSK = "ipsecpsk";
804804
public static final String GUEST_IP = "guestip";
805805
public static final String REMOVED = "removed";
806+
public static final String REMOVER_ACCOUNT_ID = "removeraccountid";
806807
public static final String REMOVAL_REASON = "removalreason";
807808
public static final String COMPLETED = "completed";
808809
public static final String IKE_VERSION = "ikeversion";

api/src/main/java/org/apache/cloudstack/api/response/IpQuarantineResponse.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ public class IpQuarantineResponse extends BaseResponse {
6060
@Param(description = "The reason for removing the IP from quarantine prematurely.")
6161
private String removalReason;
6262

63+
@SerializedName(ApiConstants.REMOVER_ACCOUNT_ID)
64+
@Param(description = "ID of the account that removed the IP from quarantine.")
65+
private String removerAccountId;
66+
6367
public IpQuarantineResponse() {
6468
super("quarantinedips");
6569
}
@@ -127,4 +131,12 @@ public String getRemovalReason() {
127131
public void setRemovalReason(String removalReason) {
128132
this.removalReason = removalReason;
129133
}
134+
135+
public String getRemoverAccountId() {
136+
return removerAccountId;
137+
}
138+
139+
public void setRemoverAccountId(String removerAccountId) {
140+
this.removerAccountId = removerAccountId;
141+
}
130142
}

engine/schema/src/main/java/com/cloud/network/vo/PublicIpQuarantineVO.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ public class PublicIpQuarantineVO implements PublicIpQuarantine {
6363
@Column(name = "removal_reason")
6464
private String removalReason = null;
6565

66+
@Column(name = "remover_account_id")
67+
private Long removerAccountId = null;
68+
6669
public PublicIpQuarantineVO() {
6770
}
6871

@@ -98,6 +101,11 @@ public String getRemovalReason() {
98101
return removalReason;
99102
}
100103

104+
@Override
105+
public Long getRemoverAccountId() {
106+
return this.removerAccountId;
107+
}
108+
101109
@Override
102110
public String getUuid() {
103111
return uuid;
@@ -111,6 +119,10 @@ public void setRemovalReason(String removalReason) {
111119
this.removalReason = removalReason;
112120
}
113121

122+
public void setRemoverAccountId(Long removerAccountId) {
123+
this.removerAccountId = removerAccountId;
124+
}
125+
114126
@Override
115127
public Date getRemoved() {
116128
return removed;

engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public void performDataMigration(Connection conn) {
7777
decryptConfigurationValuesFromAccountAndDomainScopesNotInSecureHiddenCategories(conn);
7878
migrateBackupDates(conn);
7979
addIndexes(conn);
80+
addRemoverAccountIdForeignKeyToQuarantinedIps(conn);
8081
}
8182

8283
@Override
@@ -262,4 +263,7 @@ private void addIndexes(Connection conn) {
262263
DbUpgradeUtils.addIndexIfNeeded(conn, "event", "resource_type", "resource_id");
263264
}
264265

266+
private void addRemoverAccountIdForeignKeyToQuarantinedIps(Connection conn) {
267+
DbUpgradeUtils.addForeignKey(conn, "quarantined_ips", "remover_account_id", "account", "id");
268+
}
265269
}

engine/schema/src/main/resources/META-INF/db/schema-41810to41900.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,6 @@ CREATE TABLE `cloud_usage`.`bucket_statistics` (
314314
`size` bigint unsigned COMMENT 'total size of bucket objects',
315315
PRIMARY KEY(`id`)
316316
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
317+
318+
-- Add remover account ID to quarantined IPs table.
319+
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.quarantined_ips', 'remover_account_id', 'bigint(20) unsigned DEFAULT NULL COMMENT "ID of the account that removed the IP from quarantine, foreign key to `account` table"');

server/src/main/java/com/cloud/api/ApiResponseHelper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5200,6 +5200,10 @@ public IpQuarantineResponse createQuarantinedIpsResponse(PublicIpQuarantine quar
52005200
quarantinedIpsResponse.setRemoved(quarantinedIp.getRemoved());
52015201
quarantinedIpsResponse.setEndDate(quarantinedIp.getEndDate());
52025202
quarantinedIpsResponse.setRemovalReason(quarantinedIp.getRemovalReason());
5203+
if (quarantinedIp.getRemoverAccountId() != null) {
5204+
Account removerAccount = _accountMgr.getAccount(quarantinedIp.getRemoverAccountId());
5205+
quarantinedIpsResponse.setRemoverAccountId(removerAccount.getUuid());
5206+
}
52035207
quarantinedIpsResponse.setResponseName("quarantinedip");
52045208

52055209
return quarantinedIpsResponse;

server/src/main/java/com/cloud/network/IpAddressManagerImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2471,9 +2471,11 @@ public void removePublicIpAddressFromQuarantine(Long quarantineProcessId, String
24712471
PublicIpQuarantineVO publicIpQuarantineVO = publicIpQuarantineDao.findById(quarantineProcessId);
24722472
Ip ipAddress = _ipAddressDao.findById(publicIpQuarantineVO.getPublicIpAddressId()).getAddress();
24732473
Date removedDate = new Date();
2474+
Long removerAccountId = CallContext.current().getCallingAccountId();
24742475

24752476
publicIpQuarantineVO.setRemoved(removedDate);
24762477
publicIpQuarantineVO.setRemovalReason(removalReason);
2478+
publicIpQuarantineVO.setRemoverAccountId(removerAccountId);
24772479

24782480
s_logger.debug(String.format("Removing public IP Address [%s] from quarantine by updating the removed date to [%s].", ipAddress, removedDate));
24792481
publicIpQuarantineDao.persist(publicIpQuarantineVO);

server/src/test/java/com/cloud/api/ApiResponseHelperTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
package com.cloud.api;
1818

1919
import com.cloud.domain.DomainVO;
20+
import com.cloud.network.PublicIpQuarantine;
2021
import com.cloud.network.as.AutoScaleVmGroup;
2122
import com.cloud.network.as.AutoScaleVmGroupVO;
2223
import com.cloud.network.as.AutoScaleVmProfileVO;
2324
import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao;
25+
import com.cloud.network.dao.IPAddressDao;
2426
import com.cloud.network.dao.IPAddressVO;
2527
import com.cloud.network.dao.LoadBalancerVO;
2628
import com.cloud.network.dao.NetworkServiceMapDao;
@@ -41,6 +43,7 @@
4143
import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse;
4244
import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
4345
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
46+
import org.apache.cloudstack.api.response.IpQuarantineResponse;
4447
import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
4548
import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
4649
import org.apache.cloudstack.api.response.UsageRecordResponse;
@@ -97,6 +100,9 @@ public class ApiResponseHelperTest {
97100
@Mock
98101
UserDataDao userDataDaoMock;
99102

103+
@Mock
104+
IPAddressDao ipAddressDaoMock;
105+
100106
@Spy
101107
@InjectMocks
102108
ApiResponseHelper apiResponseHelper = new ApiResponseHelper();
@@ -396,4 +402,54 @@ public void testCreateUnmanagedInstanceResponseVmwareDcVms() {
396402
Assert.assertEquals(1, response.getDisks().size());
397403
Assert.assertEquals(1, response.getNics().size());
398404
}
405+
406+
@Test
407+
public void createQuarantinedIpsResponseTestReturnsObject() {
408+
String quarantinedIpUuid = "quarantined_ip_uuid";
409+
Long previousOwnerId = 300L;
410+
String previousOwnerUuid = "previous_owner_uuid";
411+
String previousOwnerName = "previous_owner_name";
412+
Long removerAccountId = 400L;
413+
String removerAccountUuid = "remover_account_uuid";
414+
Long publicIpAddressId = 500L;
415+
String publicIpAddress = "1.2.3.4";
416+
Date created = new Date(599L);
417+
Date removed = new Date(600L);
418+
Date endDate = new Date(601L);
419+
String removalReason = "removalReason";
420+
421+
PublicIpQuarantine quarantinedIpMock = Mockito.mock(PublicIpQuarantine.class);
422+
IPAddressVO ipAddressVoMock = Mockito.mock(IPAddressVO.class);
423+
Account previousOwner = Mockito.mock(Account.class);
424+
Account removerAccount = Mockito.mock(Account.class);
425+
426+
Mockito.when(quarantinedIpMock.getUuid()).thenReturn(quarantinedIpUuid);
427+
Mockito.when(quarantinedIpMock.getPreviousOwnerId()).thenReturn(previousOwnerId);
428+
Mockito.when(quarantinedIpMock.getPublicIpAddressId()).thenReturn(publicIpAddressId);
429+
Mockito.doReturn(ipAddressVoMock).when(ipAddressDaoMock).findById(publicIpAddressId);
430+
Mockito.when(ipAddressVoMock.getAddress()).thenReturn(new Ip(publicIpAddress));
431+
Mockito.doReturn(previousOwner).when(accountManagerMock).getAccount(previousOwnerId);
432+
Mockito.when(previousOwner.getUuid()).thenReturn(previousOwnerUuid);
433+
Mockito.when(previousOwner.getName()).thenReturn(previousOwnerName);
434+
Mockito.when(quarantinedIpMock.getCreated()).thenReturn(created);
435+
Mockito.when(quarantinedIpMock.getRemoved()).thenReturn(removed);
436+
Mockito.when(quarantinedIpMock.getEndDate()).thenReturn(endDate);
437+
Mockito.when(quarantinedIpMock.getRemovalReason()).thenReturn(removalReason);
438+
Mockito.when(quarantinedIpMock.getRemoverAccountId()).thenReturn(removerAccountId);
439+
Mockito.when(removerAccount.getUuid()).thenReturn(removerAccountUuid);
440+
Mockito.doReturn(removerAccount).when(accountManagerMock).getAccount(removerAccountId);
441+
442+
IpQuarantineResponse result = apiResponseHelper.createQuarantinedIpsResponse(quarantinedIpMock);
443+
444+
Assert.assertEquals(quarantinedIpUuid, result.getId());
445+
Assert.assertEquals(publicIpAddress, result.getPublicIpAddress());
446+
Assert.assertEquals(previousOwnerUuid, result.getPreviousOwnerId());
447+
Assert.assertEquals(previousOwnerName, result.getPreviousOwnerName());
448+
Assert.assertEquals(created, result.getCreated());
449+
Assert.assertEquals(removed, result.getRemoved());
450+
Assert.assertEquals(endDate, result.getEndDate());
451+
Assert.assertEquals(removalReason, result.getRemovalReason());
452+
Assert.assertEquals(removerAccountUuid, result.getRemoverAccountId());
453+
Assert.assertEquals("quarantinedip", result.getResponseName());
454+
}
399455
}

server/src/test/java/com/cloud/network/IpAddressManagerTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@
3636
import com.cloud.network.vo.PublicIpQuarantineVO;
3737
import com.cloud.user.Account;
3838
import com.cloud.user.AccountManager;
39+
import org.apache.cloudstack.context.CallContext;
3940
import org.junit.Assert;
4041
import org.junit.Before;
4142
import org.junit.Test;
4243
import org.junit.runner.RunWith;
4344
import org.mockito.InjectMocks;
4445
import org.mockito.Mock;
46+
import org.mockito.MockedStatic;
4547
import org.mockito.Mockito;
4648
import org.mockito.Spy;
4749
import org.mockito.runners.MockitoJUnitRunner;
@@ -397,6 +399,31 @@ public void checkIfPublicIpAddressIsNotInQuarantineAndCanBeAllocatedTestIpIsInQu
397399
Assert.assertFalse(result);
398400
}
399401

402+
@Test
403+
public void removePublicIpAddressFromQuarantineTestPersistsObject() {
404+
Long quarantineProcessId = 100L;
405+
Long publicAddressId = 200L;
406+
Long callingAccountId = 300L;
407+
String removalReason = "removalReason";
408+
409+
try (MockedStatic<CallContext> callContextMockedStatic = Mockito.mockStatic(CallContext.class)) {
410+
Mockito.doReturn(publicIpQuarantineVOMock).when(publicIpQuarantineDaoMock).findById(quarantineProcessId);
411+
Mockito.when(publicIpQuarantineVOMock.getPublicIpAddressId()).thenReturn(publicAddressId);
412+
Mockito.doReturn(ipAddressVO).when(ipAddressDao).findById(publicAddressId);
413+
414+
CallContext callContextMock = Mockito.mock(CallContext.class);
415+
Mockito.when(callContextMock.getCallingAccountId()).thenReturn(callingAccountId);
416+
callContextMockedStatic.when(CallContext::current).thenReturn(callContextMock);
417+
418+
ipAddressManager.removePublicIpAddressFromQuarantine(quarantineProcessId, removalReason);
419+
420+
Mockito.verify(publicIpQuarantineVOMock).setRemoved(Mockito.any());
421+
Mockito.verify(publicIpQuarantineVOMock).setRemovalReason(removalReason);
422+
Mockito.verify(publicIpQuarantineVOMock).setRemoverAccountId(callingAccountId);
423+
Mockito.verify(publicIpQuarantineDaoMock).persist(publicIpQuarantineVOMock);
424+
}
425+
}
426+
400427
@Test
401428
public void updateSourceNatIpAddress() throws Exception {
402429
IPAddressVO requestedIp = Mockito.mock(IPAddressVO.class);

0 commit comments

Comments
 (0)