From 39e80ecf9522ee14e8c655e3d3c2003000ca197d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Fri, 6 Jun 2025 10:34:36 -0300 Subject: [PATCH 01/18] create API to list console sessions --- .../apache/cloudstack/api/ApiConstants.java | 4 + .../cloudstack/api/ResponseGenerator.java | 4 + .../consoleproxy/ListConsoleSessionsCmd.java | 173 ++++++++++++++ .../api/response/ConsoleSessionResponse.java | 224 ++++++++++++++++++ .../consoleproxy/ConsoleAccessManager.java | 7 + .../consoleproxy/ConsoleSession.java | 45 ++++ .../ListConsoleSessionsCmdTest.java | 124 ++++++++++ .../java/com/cloud/vm/ConsoleSessionVO.java | 25 +- .../com/cloud/vm/dao/ConsoleSessionDao.java | 6 + .../cloud/vm/dao/ConsoleSessionDaoImpl.java | 57 +++++ .../META-INF/db/schema-42010to42100.sql | 9 + .../java/com/cloud/api/ApiResponseHelper.java | 46 ++++ .../ConsoleAccessManagerImpl.java | 63 +++++ .../com/cloud/api/ApiResponseHelperTest.java | 125 ++++++++++ .../ConsoleAccessManagerImplTest.java | 170 +++++++++++++ tools/apidoc/gen_toc.py | 3 +- 16 files changed, 1083 insertions(+), 2 deletions(-) create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java create mode 100644 api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleSession.java create mode 100644 api/src/test/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmdTest.java diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 70d3763b0beb..660b4e8a6e8b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -23,6 +23,7 @@ public class ApiConstants { public static final String ACCOUNT_ID = "accountid"; public static final String ACCOUNT_IDS = "accountids"; public static final String ACCUMULATE = "accumulate"; + public static final String ACQUIRED = "acquired"; public static final String ACTIVATION_RULE = "activationrule"; public static final String ACTIVITY = "activity"; public static final String ADAPTER_TYPE = "adaptertype"; @@ -94,9 +95,11 @@ public class ApiConstants { public static final String CONVERT_INSTANCE_HOST_ID = "convertinstancehostid"; public static final String CONVERT_INSTANCE_STORAGE_POOL_ID = "convertinstancepoolid"; public static final String ENABLED_REVOCATION_CHECK = "enabledrevocationcheck"; + public static final String CLIENT_ADDRESS = "clientaddress"; public static final String COMBINED_CAPACITY_ORDERING = "COMBINED"; public static final String CONTROLLER = "controller"; public static final String CONTROLLER_UNIT = "controllerunit"; + public static final String CONSOLE_ENDPOINT_CREATOR_ADDRESS = "consoleendpointcreatoraddress"; public static final String COPY_IMAGE_TAGS = "copyimagetags"; public static final String CPU_OVERCOMMIT_RATIO = "cpuOvercommitRatio"; public static final String CSR = "csr"; @@ -273,6 +276,7 @@ public class ApiConstants { public static final String HYPERVISOR = "hypervisor"; public static final String INLINE = "inline"; public static final String INSTANCE = "instance"; + public static final String INSTANCE_ID = "instance_id"; public static final String ICMP_CODE = "icmpcode"; public static final String ICMP_TYPE = "icmptype"; public static final String ID = "id"; diff --git a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java index 51918570078e..8e92e877f5ca 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; +import org.apache.cloudstack.api.response.ConsoleSessionResponse; +import org.apache.cloudstack.consoleproxy.ConsoleSession; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants.HostDetails; @@ -579,4 +581,6 @@ List createTemplateResponses(ResponseView view, VirtualMachine void updateTemplateIsoResponsesForIcons(List responses, ResourceTag.ResourceObjectType type); GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin); + + ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consoleSession, ResponseView responseView); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java new file mode 100644 index 000000000000..12d487cbab73 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -0,0 +1,173 @@ +// 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.import org.apache.cloudstack.context.CallContext; +package org.apache.cloudstack.api.command.user.consoleproxy; + +import org.apache.cloudstack.consoleproxy.ConsoleSession; + +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.UserAccount; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.ConsoleSessionResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UserResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.consoleproxy.ConsoleAccessManager; + +import javax.inject.Inject; +import java.util.Date; + +@APICommand(name = "listConsoleSessions", description = "Lists console sessions.", responseObject = ConsoleSessionResponse.class, + entityType = {ConsoleSession.class}, since = "4.21.0", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}) +public class ListConsoleSessionsCmd extends BaseListCmd { + @Inject + private AccountService accountService; + + @Inject + private ConsoleAccessManager consoleAccessManager; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ConsoleSessionResponse.class, description = "The ID of the console session.") + private Long id; + + @ACL + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "The domain ID of the account that created the console endpoint.") + private Long domainId; + + @ACL + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "The ID of the account that created the console endpoint.") + private Long accountId; + + @ACL + @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "The ID of the user that created the console endpoint.") + private Long userId; + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, authorized = {RoleType.Admin}, description = "Lists console sessions from the specified host.") + private Long hostId; + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Lists console sessions acquired from this date onwards. " + + ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS) + private Date startDate; + + @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "Lists console sessions acquired up until this date. " + + ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS) + private Date endDate; + + @Parameter(name = ApiConstants.INSTANCE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, description = "The ID of the instance.") + private Long instanceId; + + @Parameter(name = ApiConstants.CONSOLE_ENDPOINT_CREATOR_ADDRESS, type = CommandType.STRING, description = "IP address of the creator of the console endpoint.") + private String consoleEndpointCreatorAddress; + + @Parameter(name = ApiConstants.CLIENT_ADDRESS, type = CommandType.STRING, description = "IP address of the client that accessed the console session.") + private String clientAddress; + + @Parameter(name = ApiConstants.ACTIVE_ONLY, type = CommandType.BOOLEAN, + description = "Lists only active console sessions, defaults to true. Active sessions are the ones that have been acquired and have not been removed.") + private boolean activeOnly = true; + + @Parameter(name = ApiConstants.IS_RECURSIVE, type = CommandType.BOOLEAN, since = "4.20.0.1-scclouds", + description = "Lists console sessions recursively per domain. If an account ID is informed, only the account's console sessions will be listed. Defaults to false.") + private boolean recursive = false; + + public Long getId() { + return id; + } + + public Long getDomainId() { + return domainId; + } + + public Long getAccountId() { + return accountId; + } + + public Long getUserId() { + return userId; + } + + public Long getHostId() { + return hostId; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + + public Long getInstanceId() { + return instanceId; + } + + public String getConsoleEndpointCreatorAddress() { + return consoleEndpointCreatorAddress; + } + + public String getClientAddress() { + return clientAddress; + } + + public boolean isActiveOnly() { + return activeOnly; + } + + public boolean isRecursive() { + return recursive; + } + + @Override + public void execute() { + ListResponse response = consoleAccessManager.listConsoleSessions(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + if (getId() != null) { + ConsoleSession consoleSession = consoleAccessManager.listConsoleSessionById(getId()); + if (consoleSession != null) { + return consoleSession.getAccountId(); + } + } + + if (getAccountId() != null) { + return getAccountId(); + } + + if (getUserId() != null) { + UserAccount userAccount = accountService.getUserAccountById(getUserId()); + if (userAccount != null) { + return userAccount.getAccountId(); + } + } + + return Account.ACCOUNT_ID_SYSTEM; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java new file mode 100644 index 000000000000..d5bc72f8c480 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java @@ -0,0 +1,224 @@ +// 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.import org.apache.cloudstack.context.CallContext; +package org.apache.cloudstack.api.response; + +import com.google.gson.annotations.SerializedName; + +import com.cloud.serializer.Param; +import org.apache.cloudstack.consoleproxy.ConsoleSession; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import java.util.Date; + +@EntityReference(value = ConsoleSession.class) +public class ConsoleSessionResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the console session.") + private String id; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "Date when the console session's endpoint was created.") + private Date created; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "Domain of the account that created the console endpoint.") + private String domain; + + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "Domain path of the account that created the console endpoint.") + private String domainPath; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "Domain ID of the account that created the console endpoint.") + private String domainId; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "Account that created the console endpoint.") + private String account; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "ID of the account that created the console endpoint.") + private String accountId; + + @SerializedName(ApiConstants.USER) + @Param(description = "User that created the console endpoint.") + private String user; + + @SerializedName(ApiConstants.USER_ID) + @Param(description = "ID of the user that created the console endpoint.") + private String userId; + + @SerializedName(ApiConstants.INSTANCE_ID) + @Param(description = "ID of the instance.") + private String instanceId; + + @SerializedName(ApiConstants.HOST_ID) + @Param(description = "ID of the host.") + private String hostId; + + @SerializedName(ApiConstants.HOST_NAME) + @Param(description = "Name of the host.") + private String hostName; + + @SerializedName(ApiConstants.ACQUIRED) + @Param(description = "Date when the console session was acquired.") + private Date acquired; + + @SerializedName(ApiConstants.REMOVED) + @Param(description = "Date when the console session was removed.") + private Date removed; + + @SerializedName(ApiConstants.CONSOLE_ENDPOINT_CREATOR_ADDRESS) + @Param(description = "IP address of the creator of the console endpoint.") + private String consoleEndpointCreatorAddress; + + @SerializedName(ApiConstants.CLIENT_ADDRESS) + @Param(description = "IP address of the client that created the console session.") + private String clientAddress; + + public void setId(String id) { + this.id = id; + } + + public void setCreated(Date created) { + this.created = created; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public void setAccount(String account) { + this.account = account; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public void setUser(String user) { + this.user = user; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public void setAcquired(Date acquired) { + this.acquired = acquired; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public void setConsoleEndpointCreatorAddress(String consoleEndpointCreatorAddress) { + this.consoleEndpointCreatorAddress = consoleEndpointCreatorAddress; + } + + public void setClientAddress(String clientAddress) { + this.clientAddress = clientAddress; + } + + public String getId() { + return id; + } + + public Date getCreated() { + return created; + } + + public String getDomain() { + return domain; + } + + public String getDomainPath() { + return domainPath; + } + + public String getDomainId() { + return domainId; + } + + public String getAccount() { + return account; + } + + public String getAccountId() { + return accountId; + } + + public String getUser() { + return user; + } + + public String getUserId() { + return userId; + } + + public String getInstanceId() { + return instanceId; + } + + public String getHostId() { + return hostId; + } + + public String getHostName() { + return hostName; + } + + public Date getAcquired() { + return acquired; + } + + public Date getRemoved() { + return removed; + } + + public String getConsoleEndpointCreatorAddress() { + return consoleEndpointCreatorAddress; + } + + public String getClientAddress() { + return clientAddress; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java index 23b571e7fae1..655b8faf443a 100644 --- a/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java +++ b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java @@ -18,6 +18,9 @@ import com.cloud.utils.component.Manager; import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint; +import org.apache.cloudstack.api.command.user.consoleproxy.ListConsoleSessionsCmd; +import org.apache.cloudstack.api.response.ConsoleSessionResponse; +import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import java.util.Date; @@ -48,4 +51,8 @@ public interface ConsoleAccessManager extends Manager, Configurable { String genAccessTicket(String host, String port, String sid, String tag, String sessionUuid); String genAccessTicket(String host, String port, String sid, String tag, Date normalizedHashTime, String sessionUuid); + + ListResponse listConsoleSessions(ListConsoleSessionsCmd cmd); + + ConsoleSession listConsoleSessionById(long id); } diff --git a/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleSession.java b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleSession.java new file mode 100644 index 000000000000..6cbdd31fd943 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleSession.java @@ -0,0 +1,45 @@ +// 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.consoleproxy; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +public interface ConsoleSession extends InternalIdentity, Identity { + + Date getCreated(); + + long getDomainId(); + + long getAccountId(); + + long getUserId(); + + long getInstanceId(); + + long getHostId(); + + Date getRemoved(); + + Date getAcquired(); + + String getConsoleEndpointCreatorAddress(); + + String getClientAddress(); +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmdTest.java new file mode 100644 index 000000000000..47bef14bb615 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmdTest.java @@ -0,0 +1,124 @@ +// 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.import org.apache.cloudstack.context.CallContext; +package org.apache.cloudstack.api.command.user.consoleproxy; + +import org.apache.cloudstack.consoleproxy.ConsoleSession; +import com.cloud.user.AccountService; + +import com.cloud.user.UserAccount; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.consoleproxy.ConsoleAccessManager; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ListConsoleSessionsCmdTest { + @Mock + private AccountService accountServiceMock; + + @Mock + private ConsoleAccessManager consoleAccessManagerMock; + + @Spy + @InjectMocks + private ListConsoleSessionsCmd listConsoleSessionsCmdSpy; + + @Test + public void executeTestApiExecutionShouldCallServiceLayer() { + Mockito.when(consoleAccessManagerMock.listConsoleSessions(listConsoleSessionsCmdSpy)).thenReturn(new ListResponse<>()); + listConsoleSessionsCmdSpy.execute(); + Mockito.verify(consoleAccessManagerMock).listConsoleSessions(listConsoleSessionsCmdSpy); + } + + @Test + public void getEntityOwnerIdTestReturnConsoleSessionIdIfProvided() { + ConsoleSession consoleSessionMock = Mockito.mock(ConsoleSession.class); + long consoleSessionId = 2L; + long accountId = 2L; + + Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(consoleSessionId); + Mockito.when(consoleAccessManagerMock.listConsoleSessionById(consoleSessionId)).thenReturn(consoleSessionMock); + Mockito.when(consoleSessionMock.getAccountId()).thenReturn(accountId); + + Assert.assertEquals(accountId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } + + @Test + public void getEntityOwnerIdTestReturnAccountIdWhenNoConsoleSessionIdIsProvided() { + long accountId = 2L; + + Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(null); + Mockito.when(listConsoleSessionsCmdSpy.getAccountId()).thenReturn(accountId); + + Assert.assertEquals(accountId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } + + @Test + public void getEntityOwnerIdTestReturnUserIdWhenNoConsoleSessionIdAndAccountIdAreProvided() { + UserAccount userAccountMock = Mockito.mock(UserAccount.class); + long userId = 2L; + + Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(null); + Mockito.when(listConsoleSessionsCmdSpy.getAccountId()).thenReturn(null); + Mockito.when(listConsoleSessionsCmdSpy.getUserId()).thenReturn(userId); + Mockito.when(accountServiceMock.getUserAccountById(userId)).thenReturn(userAccountMock); + Mockito.when(userAccountMock.getAccountId()).thenReturn(userId); + + Assert.assertEquals(userId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } + + @Test + public void getEntityOwnerIdTestReturnSystemAccountIdWhenNoConsoleSessionIdAndAccountIdAndUserIdAreProvided() { + long systemAccountId = 1L; + + Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(null); + Mockito.when(listConsoleSessionsCmdSpy.getAccountId()).thenReturn(null); + Mockito.when(listConsoleSessionsCmdSpy.getUserId()).thenReturn(null); + + Assert.assertEquals(systemAccountId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } + + @Test + public void getEntityOwnerIdTestReturnSystemAccountIdWhenConsoleSessionDoesNotExist() { + long consoleSessionId = 2L; + long systemAccountId = 1L; + + Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(consoleSessionId); + Mockito.when(consoleAccessManagerMock.listConsoleSessionById(consoleSessionId)).thenReturn(null); + + Assert.assertEquals(systemAccountId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } + + @Test + public void getEntityOwnerIdTestReturnSystemAccountIdWhenUserAccountDoesNotExist() { + long userId = 2L; + long systemAccountId = 1L; + + Mockito.when(listConsoleSessionsCmdSpy.getUserId()).thenReturn(userId); + Mockito.when(accountServiceMock.getUserAccountById(userId)).thenReturn(null); + + Assert.assertEquals(systemAccountId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } +} diff --git a/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java b/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java index ef777be2de94..d8f2838dd477 100644 --- a/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java @@ -19,6 +19,8 @@ package com.cloud.vm; +import org.apache.cloudstack.consoleproxy.ConsoleSession; + import java.util.Date; import javax.persistence.Column; @@ -32,7 +34,7 @@ @Entity @Table(name = "console_session") -public class ConsoleSessionVO { +public class ConsoleSessionVO implements ConsoleSession { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -45,6 +47,9 @@ public class ConsoleSessionVO { @Column(name = "created") private Date created; + @Column(name = "domain_id") + private long domainId; + @Column(name = "account_id") private long accountId; @@ -86,6 +91,7 @@ public void setUuid(String uuid) { this.uuid = uuid; } + @Override public Date getCreated() { return created; } @@ -94,6 +100,16 @@ public void setCreated(Date created) { this.created = created; } + @Override + public long getDomainId() { + return domainId; + } + + public void setDomainId(long domainId) { + this.domainId = domainId; + } + + @Override public long getAccountId() { return accountId; } @@ -102,6 +118,7 @@ public void setAccountId(long accountId) { this.accountId = accountId; } + @Override public long getUserId() { return userId; } @@ -110,6 +127,7 @@ public void setUserId(long userId) { this.userId = userId; } + @Override public long getInstanceId() { return instanceId; } @@ -118,6 +136,7 @@ public void setInstanceId(long instanceId) { this.instanceId = instanceId; } + @Override public long getHostId() { return hostId; } @@ -126,6 +145,7 @@ public void setHostId(long hostId) { this.hostId = hostId; } + @Override public Date getRemoved() { return removed; } @@ -134,6 +154,7 @@ public void setRemoved(Date removed) { this.removed = removed; } + @Override public Date getAcquired() { return acquired; } @@ -142,6 +163,7 @@ public void setAcquired(Date acquired) { this.acquired = acquired; } + @Override public String getConsoleEndpointCreatorAddress() { return consoleEndpointCreatorAddress; } @@ -150,6 +172,7 @@ public void setConsoleEndpointCreatorAddress(String consoleEndpointCreatorAddres this.consoleEndpointCreatorAddress = consoleEndpointCreatorAddress; } + @Override public String getClientAddress() { return clientAddress; } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java index 95ced889b3db..bd199fe2eb3b 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java @@ -19,6 +19,7 @@ package com.cloud.vm.dao; +import com.cloud.utils.Pair; import com.cloud.vm.ConsoleSessionVO; import com.cloud.utils.db.GenericDao; @@ -36,4 +37,9 @@ public interface ConsoleSessionDao extends GenericDao { void acquireSession(String sessionUuid, String clientAddress); int expungeByVmList(List vmIds, Long batchSize); + + Pair, Integer> listConsoleSessions(Long id, List domainIds, Long accountId, Long userId, Long hostId, + Date startDate, Date endDate, Long instanceId, + String consoleEndpointCreatorAddress, String clientAddress, + boolean activeOnly, Long pageSizeVal, Long startIndex); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java index 3d1178946700..28d875a7f0b8 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java @@ -22,6 +22,8 @@ import java.util.Date; import java.util.List; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; import org.apache.commons.collections.CollectionUtils; import com.cloud.utils.db.GenericDaoBase; @@ -80,4 +82,59 @@ public int expungeByVmList(List vmIds, Long batchSize) { sc.setParameters("vmIds", vmIds.toArray()); return batchExpunge(sc, batchSize); } + + @Override + public Pair, Integer> listConsoleSessions(Long id, List domainIds, Long accountId, Long userId, Long hostId, + Date startDate, Date endDate, Long instanceId, + String consoleEndpointCreatorAddress, String clientAddress, + boolean activeOnly, Long pageSizeVal, Long startIndex) { + Filter filter = new Filter(ConsoleSessionVO.class, "created", false, startIndex, pageSizeVal); + SearchCriteria searchCriteria = createListConsoleSessionsSearchCriteria(id, domainIds, accountId, userId, hostId, + startDate, endDate, instanceId, consoleEndpointCreatorAddress, clientAddress, activeOnly); + + return searchAndCount(searchCriteria, filter, true); + } + + private SearchCriteria createListConsoleSessionsSearchCriteria(Long id, List domainIds, Long accountId, Long userId, Long hostId, + Date startDate, Date endDate, Long instanceId, + String consoleEndpointCreatorAddress, String clientAddress, + boolean activeOnly) { + SearchCriteria searchCriteria = createListConsoleSessionsSearchBuilder(activeOnly).create(); + + searchCriteria.setParametersIfNotNull("id", id); + searchCriteria.setParametersIfNotNull("domainIds", domainIds.toArray()); + searchCriteria.setParametersIfNotNull("accountId", accountId); + searchCriteria.setParametersIfNotNull("userId", userId); + searchCriteria.setParametersIfNotNull("hostId", hostId); + searchCriteria.setParametersIfNotNull("instanceId", instanceId); + searchCriteria.setParametersIfNotNull("startDate", startDate); + searchCriteria.setParametersIfNotNull("endDate", endDate); + searchCriteria.setParametersIfNotNull("creatorAddress", consoleEndpointCreatorAddress); + searchCriteria.setParametersIfNotNull("clientAddress", clientAddress); + + return searchCriteria; + } + + private SearchBuilder createListConsoleSessionsSearchBuilder(boolean activeOnly) { + SearchBuilder searchBuilder = createSearchBuilder(); + + searchBuilder.and("id", searchBuilder.entity().getId(), SearchCriteria.Op.EQ); + searchBuilder.and("domainIds", searchBuilder.entity().getDomainId(), SearchCriteria.Op.IN); + searchBuilder.and("accountId", searchBuilder.entity().getAccountId(), SearchCriteria.Op.EQ); + searchBuilder.and("userId", searchBuilder.entity().getUserId(), SearchCriteria.Op.EQ); + searchBuilder.and("hostId", searchBuilder.entity().getHostId(), SearchCriteria.Op.EQ); + searchBuilder.and("instanceId", searchBuilder.entity().getInstanceId(), SearchCriteria.Op.EQ); + searchBuilder.and("startDate", searchBuilder.entity().getAcquired(), SearchCriteria.Op.GTEQ); + searchBuilder.and("endDate", searchBuilder.entity().getAcquired(), SearchCriteria.Op.LTEQ); + searchBuilder.and("creatorAddress", searchBuilder.entity().getConsoleEndpointCreatorAddress(), SearchCriteria.Op.EQ); + searchBuilder.and("clientAddress", searchBuilder.entity().getClientAddress(), SearchCriteria.Op.EQ); + + if (activeOnly) { + searchBuilder.and("acquired", searchBuilder.entity().getAcquired(), SearchCriteria.Op.NNULL); + searchBuilder.and("removed", searchBuilder.entity().getRemoved(), SearchCriteria.Op.NULL); + } + + searchBuilder.done(); + return searchBuilder; + } } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql index 4677c8bbd3f7..6b2b27f33614 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql @@ -745,3 +745,12 @@ JOIN ( GROUP BY object_store_id ) buckets_quota_sum_view ON `object_store`.id = buckets_quota_sum_view.object_store_id SET `object_store`.allocated_size = buckets_quota_sum_view.total_quota; + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'domain_id', 'bigint(20) unsigned NOT NULL'); + +UPDATE `cloud`.`console_session` `cs` +SET `cs`.`domain_id` = ( + SELECT `acc`.`domain_id` + FROM `cloud`.`account` `acc` + WHERE `acc`.`id` = `cs`.`account_id` +); diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 41433cb3e6f8..23f7ae9167e6 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -87,6 +87,7 @@ import org.apache.cloudstack.api.response.ConfigurationGroupResponse; import org.apache.cloudstack.api.response.ConfigurationResponse; import org.apache.cloudstack.api.response.ConfigurationSubGroupResponse; +import org.apache.cloudstack.api.response.ConsoleSessionResponse; import org.apache.cloudstack.api.response.ControlledEntityResponse; import org.apache.cloudstack.api.response.ControlledViewEntityResponse; import org.apache.cloudstack.api.response.CounterResponse; @@ -205,6 +206,7 @@ import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.config.ConfigurationGroup; import org.apache.cloudstack.config.ConfigurationSubGroup; +import org.apache.cloudstack.consoleproxy.ConsoleSession; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.direct.download.DirectDownloadCertificate; import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap; @@ -5614,4 +5616,48 @@ public GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin) { return guiThemeResponse; } + + @Override + public ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consoleSession, ResponseView responseView) { + ConsoleSessionResponse consoleSessionResponse = new ConsoleSessionResponse(); + consoleSessionResponse.setId(consoleSession.getUuid()); + consoleSessionResponse.setCreated(consoleSession.getCreated()); + consoleSessionResponse.setAcquired(consoleSession.getAcquired()); + consoleSessionResponse.setRemoved(consoleSession.getRemoved()); + consoleSessionResponse.setConsoleEndpointCreatorAddress(consoleSession.getConsoleEndpointCreatorAddress()); + consoleSessionResponse.setClientAddress(consoleSession.getClientAddress()); + + Domain domain = ApiDBUtils.findDomainById(consoleSession.getDomainId()); + if (domain != null) { + consoleSessionResponse.setDomain(domain.getName()); + consoleSessionResponse.setDomainPath(domain.getPath()); + consoleSessionResponse.setDomainId(domain.getUuid()); + } + + User user = findUserById(consoleSession.getUserId()); + if (user != null) { + consoleSessionResponse.setUser(user.getUsername()); + consoleSessionResponse.setUserId(user.getUuid()); + } + + Account account = ApiDBUtils.findAccountById(consoleSession.getAccountId()); + if (account != null) { + consoleSessionResponse.setAccount(account.getAccountName()); + consoleSessionResponse.setAccountId(account.getUuid()); + } + + Host host = findHostById(consoleSession.getHostId()); + if (responseView == ResponseView.Full && host != null) { + consoleSessionResponse.setHostId(host.getUuid()); + consoleSessionResponse.setHostName(host.getName()); + } + + VMInstanceVO instance = ApiDBUtils.findVMInstanceById(consoleSession.getInstanceId()); + if (instance != null) { + consoleSessionResponse.setInstanceId(instance.getUuid()); + } + + consoleSessionResponse.setObjectName("consolesession"); + return consoleSessionResponse; + } } diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java index 0ab81a4c1d6d..2015248f41bb 100644 --- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.consoleproxy; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -27,7 +28,13 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.domain.dao.DomainDao; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint; +import org.apache.cloudstack.api.command.user.consoleproxy.ListConsoleSessionsCmd; +import org.apache.cloudstack.api.response.ConsoleSessionResponse; +import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.security.keys.KeysManager; import org.apache.commons.codec.binary.Base64; @@ -86,6 +93,8 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce @Inject private AccountManager accountManager; @Inject + private DomainDao domainDao; + @Inject private VirtualMachineManager virtualMachineManager; @Inject private ManagementServer managementServer; @@ -103,6 +112,8 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce DataCenterDao dataCenterDao; @Inject private ConsoleSessionDao consoleSessionDao; + @Inject + private ResponseGenerator responseGenerator; private ScheduledExecutorService executorService = null; @@ -181,6 +192,58 @@ private void reallyRun() { } } + @Override + public ListResponse listConsoleSessions(ListConsoleSessionsCmd cmd) { + Pair, Integer> consoleSessions = listConsoleSessionsInternal(cmd); + ListResponse response = new ListResponse<>(); + + ResponseObject.ResponseView responseView = ResponseObject.ResponseView.Restricted; + Long callerId = CallContext.current().getCallingAccountId(); + if (accountManager.isRootAdmin(callerId)) { + responseView = ResponseObject.ResponseView.Full; + } + + List consoleSessionResponses = new ArrayList<>(); + for (ConsoleSessionVO consoleSession : consoleSessions.first()) { + ConsoleSessionResponse consoleSessionResponse = responseGenerator.createConsoleSessionResponse(consoleSession, responseView); + consoleSessionResponses.add(consoleSessionResponse); + } + + response.setResponses(consoleSessionResponses, consoleSessions.second()); + return response; + } + + protected Pair, Integer> listConsoleSessionsInternal(ListConsoleSessionsCmd cmd) { + CallContext caller = CallContext.current(); + long domainId = cmd.getDomainId() != null ? cmd.getDomainId() : caller.getCallingAccount().getDomainId(); + Long accountId = cmd.getAccountId(); + Long userId = cmd.getUserId(); + boolean isRecursive = cmd.isRecursive(); + + boolean isCallerNormalUser = accountManager.isNormalUser(caller.getCallingAccountId()); + if (isCallerNormalUser) { + accountId = caller.getCallingAccountId(); + userId = caller.getCallingUserId(); + } + + List domainIds; + if (isRecursive) { + domainIds = domainDao.getDomainAndChildrenIds(domainId); + } else { + domainIds = List.of(domainId); + } + + return consoleSessionDao.listConsoleSessions(cmd.getId(), domainIds, accountId, userId, + cmd.getHostId(), cmd.getStartDate(), cmd.getEndDate(), cmd.getInstanceId(), + cmd.getConsoleEndpointCreatorAddress(), cmd.getClientAddress(), cmd.isActiveOnly(), + cmd.getPageSizeVal(), cmd.getStartIndex()); + } + + @Override + public ConsoleSession listConsoleSessionById(long id) { + return consoleSessionDao.findById(id); + } + @Override public ConsoleEndpoint generateConsoleEndpoint(Long vmId, String extraSecurityToken, String clientAddress) { try { diff --git a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java index a5fc791de990..74c8e4872658 100644 --- a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java +++ b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java @@ -67,6 +67,7 @@ import com.cloud.capacity.Capacity; import com.cloud.configuration.Resource; import com.cloud.domain.DomainVO; +import com.cloud.host.HostVO; import com.cloud.network.PublicIpQuarantine; import com.cloud.network.as.AutoScaleVmGroup; import com.cloud.network.as.AutoScaleVmGroupVO; @@ -93,7 +94,11 @@ import com.cloud.user.UserVO; import com.cloud.user.dao.UserDataDao; import com.cloud.utils.net.Ip; +import com.cloud.vm.ConsoleSessionVO; import com.cloud.vm.NicSecondaryIp; +import com.cloud.vm.VMInstanceVO; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.response.ConsoleSessionResponse; @RunWith(MockitoJUnitRunner.class) public class ApiResponseHelperTest { @@ -124,6 +129,19 @@ public class ApiResponseHelperTest { @Mock ResourceIconManager resourceIconManager; + @Mock + private ConsoleSessionVO consoleSessionMock; + @Mock + private DomainVO domainVOMock; + @Mock + private UserVO userVOMock; + @Mock + private AccountVO accountVOMock; + @Mock + private HostVO hostVOMock; + @Mock + private VMInstanceVO vmInstanceVOMock; + @Spy @InjectMocks ApiResponseHelper apiResponseHelper = new ApiResponseHelper(); @@ -631,4 +649,111 @@ public void testUpdateTemplateIsoResponsesForIcons_emptyInput() { Mockito.verify(resourceIconManager, Mockito.never()).getByResourceTypeAndUuids(Mockito.any(), Mockito.anyCollection()); } + + private ConsoleSessionResponse getExpectedConsoleSessionResponseForTests(boolean fullView) { + ConsoleSessionResponse expected = new ConsoleSessionResponse(); + expected.setId("uuid"); + expected.setCreated(new Date()); + expected.setAcquired(new Date()); + expected.setRemoved(new Date()); + expected.setConsoleEndpointCreatorAddress("127.0.0.1"); + expected.setClientAddress("127.0.0.1"); + + if (fullView) { + expected.setDomain("domain"); + expected.setDomainPath("domainPath"); + expected.setDomainId("domainUuid"); + expected.setUser("user"); + expected.setUserId("userUuid"); + expected.setAccount("account"); + expected.setAccountId("accountUuid"); + expected.setHostName("host"); + expected.setHostId("hostUuid"); + expected.setInstanceId("vmUuid"); + } + + return expected; + } + + @Test + public void createConsoleSessionResponseTestShouldReturnRestrictedResponse() { + ConsoleSessionResponse expected = getExpectedConsoleSessionResponseForTests(false); + + try (MockedStatic apiDBUtilsStaticMock = Mockito.mockStatic(ApiDBUtils.class)) { + Mockito.when(consoleSessionMock.getUuid()).thenReturn(expected.getId()); + Mockito.when(consoleSessionMock.getDomainId()).thenReturn(2L); + Mockito.when(consoleSessionMock.getCreated()).thenReturn(expected.getCreated()); + Mockito.when(consoleSessionMock.getAcquired()).thenReturn(expected.getAcquired()); + Mockito.when(consoleSessionMock.getRemoved()).thenReturn(expected.getRemoved()); + Mockito.when(consoleSessionMock.getConsoleEndpointCreatorAddress()).thenReturn(expected.getConsoleEndpointCreatorAddress()); + Mockito.when(consoleSessionMock.getClientAddress()).thenReturn(expected.getClientAddress()); + + ConsoleSessionResponse response = apiResponseHelper.createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Restricted); + + Assert.assertEquals(expected.getId(), response.getId()); + Assert.assertEquals(expected.getCreated(), response.getCreated()); + Assert.assertEquals(expected.getAcquired(), response.getAcquired()); + Assert.assertEquals(expected.getRemoved(), response.getRemoved()); + Assert.assertEquals(expected.getConsoleEndpointCreatorAddress(), response.getConsoleEndpointCreatorAddress()); + Assert.assertEquals(expected.getClientAddress(), response.getClientAddress()); + } + } + + @Test + public void createConsoleSessionResponseTestShouldReturnFullResponse() { + ConsoleSessionResponse expected = getExpectedConsoleSessionResponseForTests(true); + + try (MockedStatic apiDBUtilsStaticMock = Mockito.mockStatic(ApiDBUtils.class)) { + Mockito.when(consoleSessionMock.getUuid()).thenReturn(expected.getId()); + Mockito.when(consoleSessionMock.getDomainId()).thenReturn(2L); + Mockito.when(consoleSessionMock.getAccountId()).thenReturn(2L); + Mockito.when(consoleSessionMock.getUserId()).thenReturn(2L); + Mockito.when(consoleSessionMock.getHostId()).thenReturn(2L); + Mockito.when(consoleSessionMock.getInstanceId()).thenReturn(2L); + Mockito.when(consoleSessionMock.getCreated()).thenReturn(expected.getCreated()); + Mockito.when(consoleSessionMock.getAcquired()).thenReturn(expected.getAcquired()); + Mockito.when(consoleSessionMock.getRemoved()).thenReturn(expected.getRemoved()); + Mockito.when(consoleSessionMock.getConsoleEndpointCreatorAddress()).thenReturn(expected.getConsoleEndpointCreatorAddress()); + Mockito.when(consoleSessionMock.getClientAddress()).thenReturn(expected.getClientAddress()); + + apiDBUtilsStaticMock.when(() -> ApiDBUtils.findDomainById(2L)).thenReturn(domainVOMock); + Mockito.when(domainVOMock.getName()).thenReturn(expected.getDomain()); + Mockito.when(domainVOMock.getPath()).thenReturn(expected.getDomainPath()); + Mockito.when(domainVOMock.getUuid()).thenReturn(expected.getDomainId()); + + Mockito.when(apiResponseHelper.findUserById(2L)).thenReturn(userVOMock); + Mockito.when(userVOMock.getUsername()).thenReturn(expected.getUser()); + Mockito.when(userVOMock.getUuid()).thenReturn(expected.getUserId()); + + Mockito.when(ApiDBUtils.findAccountById(2L)).thenReturn(accountVOMock); + Mockito.when(accountVOMock.getAccountName()).thenReturn(expected.getAccount()); + Mockito.when(accountVOMock.getUuid()).thenReturn(expected.getAccountId()); + + Mockito.when(apiResponseHelper.findHostById(2L)).thenReturn(hostVOMock); + Mockito.when(hostVOMock.getUuid()).thenReturn(expected.getHostId()); + Mockito.when(hostVOMock.getName()).thenReturn(expected.getHostName()); + + apiDBUtilsStaticMock.when(() -> ApiDBUtils.findVMInstanceById(2L)).thenReturn(vmInstanceVOMock); + Mockito.when(vmInstanceVOMock.getUuid()).thenReturn(expected.getInstanceId()); + + ConsoleSessionResponse response = apiResponseHelper.createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Full); + + Assert.assertEquals(expected.getId(), response.getId()); + Assert.assertEquals(expected.getCreated(), response.getCreated()); + Assert.assertEquals(expected.getAcquired(), response.getAcquired()); + Assert.assertEquals(expected.getRemoved(), response.getRemoved()); + Assert.assertEquals(expected.getConsoleEndpointCreatorAddress(), response.getConsoleEndpointCreatorAddress()); + Assert.assertEquals(expected.getClientAddress(), response.getClientAddress()); + Assert.assertEquals(expected.getDomain(), response.getDomain()); + Assert.assertEquals(expected.getDomainPath(), response.getDomainPath()); + Assert.assertEquals(expected.getDomainId(), response.getDomainId()); + Assert.assertEquals(expected.getUser(), response.getUser()); + Assert.assertEquals(expected.getUserId(), response.getUserId()); + Assert.assertEquals(expected.getAccount(), response.getAccount()); + Assert.assertEquals(expected.getAccountId(), response.getAccountId()); + Assert.assertEquals(expected.getHostId(), response.getHostId()); + Assert.assertEquals(expected.getHostName(), response.getHostName()); + Assert.assertEquals(expected.getInstanceId(), response.getInstanceId()); + } + } } diff --git a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java index 748fe19893a7..c7737927a482 100644 --- a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java @@ -17,21 +17,31 @@ package org.apache.cloudstack.consoleproxy; import com.cloud.agent.AgentManager; +import com.cloud.domain.dao.DomainDao; import com.cloud.exception.PermissionDeniedException; import com.cloud.server.ManagementServer; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.utils.Pair; import com.cloud.utils.db.EntityManager; +import com.cloud.vm.ConsoleSessionVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.dao.ConsoleSessionDao; import com.cloud.vm.dao.VMInstanceDetailsDao; import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.command.user.consoleproxy.ListConsoleSessionsCmd; +import org.apache.cloudstack.api.response.ConsoleSessionResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.security.keys.KeysManager; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; @@ -66,6 +76,21 @@ public class ConsoleAccessManagerImplTest { @Mock Account account; + @Mock + private CallContext callContextMock; + @Mock + private DomainDao domainDaoMock; + @Mock + private ConsoleSessionVO consoleSessionMock; + @Mock + private ConsoleSessionDao consoleSessionDaoMock; + @Mock + private ConsoleSessionResponse consoleSessionResponseMock; + @Mock + private ListConsoleSessionsCmd listConsoleSessionsCmdMock; + @Mock + private ResponseGenerator responseGeneratorMock; + @Test public void testCheckSessionPermissionAdminAccount() { Mockito.when(account.getId()).thenReturn(1L); @@ -106,4 +131,149 @@ public void testCheckSessionPermissionForUsersOnSystemVms() { Assert.assertFalse(consoleAccessManager.checkSessionPermission(virtualMachine, account)); } } + + @Test + public void listConsoleSessionsInternalTestNormalUsersShouldOnlyBeAllowedToListTheirOwnConsoleSessions() { + long callerDomainId = 5L; + long callerAccountId = 5L; + long callerUserId = 5L; + boolean isRecursive = false; + + try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { + callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); + Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(null); + Mockito.when(callContextMock.getCallingAccount()).thenReturn(account); + Mockito.when(account.getDomainId()).thenReturn(callerDomainId); + Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive); + Mockito.when(accountManager.isNormalUser(callerAccountId)).thenReturn(true); + Mockito.when(callContextMock.getCallingAccountId()).thenReturn(callerAccountId); + Mockito.when(callContextMock.getCallingUserId()).thenReturn(callerUserId); + + consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock); + } + + Mockito.verify(consoleSessionDaoMock).listConsoleSessions( + Mockito.any(), Mockito.eq(List.of(callerDomainId)), Mockito.eq(callerAccountId), + Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() + ); + } + + @Test + public void listConsoleSessionsInternalTestAdminsShouldBeAllowedToRetrieveOtherAccountsConsoleSessions() { + long callerDomainId = 5L; + long callerAccountId = 5L; + long callerUserId = 5L; + boolean isRecursive = false; + + try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { + callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); + Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(callerDomainId); + Mockito.when(listConsoleSessionsCmdMock.getAccountId()).thenReturn(callerAccountId); + Mockito.when(listConsoleSessionsCmdMock.getUserId()).thenReturn(callerUserId); + Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive); + Mockito.when(callContextMock.getCallingAccountId()).thenReturn(callerAccountId); + Mockito.when(accountManager.isNormalUser(callerAccountId)).thenReturn(false); + + consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock); + } + + Mockito.verify(consoleSessionDaoMock).listConsoleSessions( + Mockito.any(), Mockito.eq(List.of(callerDomainId)), Mockito.eq(callerAccountId), + Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() + ); + } + + + @Test + public void listConsoleSessionsInternalTestShouldNotFetchConsoleSessionsRecursivelyWhenIsRecursiveIsFalse() { + long callerDomainId = 5L; + long callerAccountId = 5L; + long callerUserId = 5L; + boolean isRecursive = false; + + try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { + callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); + Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(callerDomainId); + Mockito.when(listConsoleSessionsCmdMock.getAccountId()).thenReturn(callerAccountId); + Mockito.when(listConsoleSessionsCmdMock.getUserId()).thenReturn(callerUserId); + Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive); + Mockito.when(callContextMock.getCallingAccountId()).thenReturn(callerAccountId); + Mockito.when(accountManager.isNormalUser(callerAccountId)).thenReturn(false); + + consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock); + } + + Mockito.verify(consoleSessionDaoMock).listConsoleSessions( + Mockito.any(), Mockito.eq(List.of(callerDomainId)), Mockito.eq(callerAccountId), + Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() + ); + } + + @Test + public void listConsoleSessionsInternalTestShouldFetchConsoleSessionsRecursivelyWhenIsRecursiveIsTrue() { + long callerDomainId = 5L; + long callerAccountId = 5L; + long callerUserId = 5L; + boolean isRecursive = true; + List domainIdsCallerHasAccessTo = List.of(callerDomainId, 6L, 7L); + + try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { + callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); + Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(callerDomainId); + Mockito.when(listConsoleSessionsCmdMock.getAccountId()).thenReturn(callerAccountId); + Mockito.when(listConsoleSessionsCmdMock.getUserId()).thenReturn(callerUserId); + Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive); + Mockito.when(callContextMock.getCallingAccountId()).thenReturn(callerAccountId); + Mockito.when(accountManager.isNormalUser(callerAccountId)).thenReturn(false); + Mockito.when(domainDaoMock.getDomainAndChildrenIds(callerDomainId)).thenReturn(domainIdsCallerHasAccessTo); + + consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock); + } + + Mockito.verify(consoleSessionDaoMock).listConsoleSessions( + Mockito.any(), Mockito.eq(domainIdsCallerHasAccessTo), Mockito.eq(callerAccountId), + Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() + ); + } + + @Test + public void listConsoleSessionsTestShouldCreateResponsesWithFullViewForRootAdmins() { + Mockito.when(consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock)).thenReturn(new Pair<>(List.of(consoleSessionMock), 1)); + + try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { + callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); + Mockito.when(callContextMock.getCallingAccountId()).thenReturn(2L); + Mockito.when(accountManager.isRootAdmin(2L)).thenReturn(true); + Mockito.when(responseGeneratorMock.createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Full)).thenReturn(consoleSessionResponseMock); + + consoleAccessManager.listConsoleSessions(listConsoleSessionsCmdMock); + } + Mockito.verify(responseGeneratorMock).createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Full); + } + + @Test + public void listConsoleSessionsTestShouldCreateResponsesWithRestrictedViewForNonRootAdmins() { + Mockito.when(consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock)).thenReturn(new Pair<>(List.of(consoleSessionMock), 1)); + + try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { + callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); + Mockito.when(callContextMock.getCallingAccountId()).thenReturn(2L); + Mockito.when(accountManager.isRootAdmin(2L)).thenReturn(false); + Mockito.when(responseGeneratorMock.createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Restricted)).thenReturn(consoleSessionResponseMock); + + consoleAccessManager.listConsoleSessions(listConsoleSessionsCmdMock); + } + + Mockito.verify(responseGeneratorMock).createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Restricted); + } + + @Test + public void listConsoleSessionByIdTestShouldCallDbLayer() { + consoleAccessManager.listConsoleSessionById(1L); + Mockito.verify(consoleSessionDaoMock).findById(1L); + } } diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index bcaaca2e39a4..ad72b4c79381 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -228,7 +228,8 @@ 'Rolling': 'Rolling Maintenance', 'importVsphereStoragePolicies' : 'vSphere storage policies', 'listVsphereStoragePolicies' : 'vSphere storage policies', - 'ConsoleEndpoint': 'Console Endpoint', + 'createConsoleEndpoint': 'Console Session', + 'listConsoleSessions': 'Console Session', 'importVm': 'Virtual Machine', 'revertToVMSnapshot': 'Virtual Machine', 'listQuarantinedIp': 'IP Quarantine', From 1ccdab3eea774ed96e7e7f1e87704deabe4bed1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Wed, 11 Jun 2025 09:33:56 -0300 Subject: [PATCH 02/18] add listConsoleSessions API to list of MS APIs --- .../src/main/java/com/cloud/server/ManagementServerImpl.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 4cd16b42ae94..3245acfbbf2c 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -395,6 +395,7 @@ import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; import org.apache.cloudstack.api.command.user.config.ListCapabilitiesCmd; import org.apache.cloudstack.api.command.user.consoleproxy.CreateConsoleEndpointCmd; +import org.apache.cloudstack.api.command.user.consoleproxy.ListConsoleSessionsCmd; import org.apache.cloudstack.api.command.user.event.ArchiveEventsCmd; import org.apache.cloudstack.api.command.user.event.DeleteEventsCmd; import org.apache.cloudstack.api.command.user.event.ListEventTypesCmd; @@ -4268,8 +4269,12 @@ public List> getCommands() { cmdList.add(ConfigureOutOfBandManagementCmd.class); cmdList.add(IssueOutOfBandManagementPowerActionCmd.class); cmdList.add(ChangeOutOfBandManagementPasswordCmd.class); + cmdList.add(GetUserKeysCmd.class); + + // Console Session APIs cmdList.add(CreateConsoleEndpointCmd.class); + cmdList.add(ListConsoleSessionsCmd.class); //user data APIs cmdList.add(RegisterUserDataCmd.class); From ccaf59219447b2fcf5da6f31bb3d1978b70d86dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Wed, 11 Jun 2025 09:34:20 -0300 Subject: [PATCH 03/18] persist console endpoint creator domain ID --- .../cloudstack/consoleproxy/ConsoleAccessManagerImpl.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java index 2015248f41bb..0e2644632176 100644 --- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java @@ -474,10 +474,13 @@ private ConsoleEndpoint composeConsoleAccessEndpoint(String rootUrl, VirtualMach } protected void persistConsoleSession(String sessionUuid, long instanceId, long hostId, String consoleEndpointCreatorAddress) { + CallContext caller = CallContext.current(); + ConsoleSessionVO consoleSessionVo = new ConsoleSessionVO(); consoleSessionVo.setUuid(sessionUuid); - consoleSessionVo.setAccountId(CallContext.current().getCallingAccountId()); - consoleSessionVo.setUserId(CallContext.current().getCallingUserId()); + consoleSessionVo.setDomainId(caller.getCallingAccount().getDomainId()); + consoleSessionVo.setAccountId(caller.getCallingAccountId()); + consoleSessionVo.setUserId(caller.getCallingUserId()); consoleSessionVo.setInstanceId(instanceId); consoleSessionVo.setHostId(hostId); consoleSessionVo.setConsoleEndpointCreatorAddress(consoleEndpointCreatorAddress); From 5b87556220234c688fc29a71d1ce749750e01885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Wed, 11 Jun 2025 12:43:39 -0300 Subject: [PATCH 04/18] fix invalid access validation performed to the domainId attribute --- .../consoleproxy/ListConsoleSessionsCmd.java | 1 - .../ConsoleAccessManagerImpl.java | 22 +++++++++++- .../ConsoleAccessManagerImplTest.java | 36 +++++++++++++++++-- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java index 12d487cbab73..b73e66733609 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -53,7 +53,6 @@ public class ListConsoleSessionsCmd extends BaseListCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ConsoleSessionResponse.class, description = "The ID of the console session.") private Long id; - @ACL @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "The domain ID of the account that created the console endpoint.") private Long domainId; diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java index 0e2644632176..dba9c64e8da8 100644 --- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java @@ -215,7 +215,7 @@ public ListResponse listConsoleSessions(ListConsoleSessi protected Pair, Integer> listConsoleSessionsInternal(ListConsoleSessionsCmd cmd) { CallContext caller = CallContext.current(); - long domainId = cmd.getDomainId() != null ? cmd.getDomainId() : caller.getCallingAccount().getDomainId(); + long domainId = getBaseDomainIdToListConsoleSessions(cmd.getDomainId()); Long accountId = cmd.getAccountId(); Long userId = cmd.getUserId(); boolean isRecursive = cmd.isRecursive(); @@ -239,6 +239,26 @@ protected Pair, Integer> listConsoleSessionsInternal(List cmd.getPageSizeVal(), cmd.getStartIndex()); } + /** + * Determines the base domain ID for listing console sessions. + * + * If no domain ID is provided, returns the caller's domain ID. Otherwise, + * checks if the caller has access to that domain and returns the provided domain ID. + * + * @param domainId The domain ID to check, can be null + * @return The base domain ID to use for listing console sessions + * @throws PermissionDeniedException if the caller does not have access to the specified domain + */ + protected long getBaseDomainIdToListConsoleSessions(Long domainId) { + Account caller = CallContext.current().getCallingAccount(); + if (domainId == null) { + return caller.getDomainId(); + } + + accountManager.checkAccess(caller, domainDao.findById(domainId)); + return domainId; + } + @Override public ConsoleSession listConsoleSessionById(long id) { return consoleSessionDao.findById(id); diff --git a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java index c7737927a482..d45ab0e47f92 100644 --- a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.consoleproxy; import com.cloud.agent.AgentManager; +import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; import com.cloud.exception.PermissionDeniedException; import com.cloud.server.ManagementServer; @@ -81,6 +82,8 @@ public class ConsoleAccessManagerImplTest { @Mock private DomainDao domainDaoMock; @Mock + private DomainVO domainMock; + @Mock private ConsoleSessionVO consoleSessionMock; @Mock private ConsoleSessionDao consoleSessionDaoMock; @@ -242,7 +245,9 @@ public void listConsoleSessionsInternalTestShouldFetchConsoleSessionsRecursively @Test public void listConsoleSessionsTestShouldCreateResponsesWithFullViewForRootAdmins() { - Mockito.when(consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock)).thenReturn(new Pair<>(List.of(consoleSessionMock), 1)); + Mockito.doReturn(new Pair<>(List.of(consoleSessionMock), 1)) + .when(consoleAccessManager) + .listConsoleSessionsInternal(listConsoleSessionsCmdMock); try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); @@ -257,7 +262,9 @@ public void listConsoleSessionsTestShouldCreateResponsesWithFullViewForRootAdmin @Test public void listConsoleSessionsTestShouldCreateResponsesWithRestrictedViewForNonRootAdmins() { - Mockito.when(consoleAccessManager.listConsoleSessionsInternal(listConsoleSessionsCmdMock)).thenReturn(new Pair<>(List.of(consoleSessionMock), 1)); + Mockito.doReturn(new Pair<>(List.of(consoleSessionMock), 1)) + .when(consoleAccessManager) + .listConsoleSessionsInternal(listConsoleSessionsCmdMock); try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); @@ -271,6 +278,31 @@ public void listConsoleSessionsTestShouldCreateResponsesWithRestrictedViewForNon Mockito.verify(responseGeneratorMock).createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Restricted); } + @Test + public void getBaseDomainIdToListConsoleSessionsTestIfNoDomainIdIsProvidedReturnCallersDomainId() { + long callerDomainId = 5L; + + try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { + callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); + Mockito.when(callContextMock.getCallingAccount()).thenReturn(account); + Mockito.when(account.getDomainId()).thenReturn(callerDomainId); + Assert.assertEquals(callerDomainId, consoleAccessManager.getBaseDomainIdToListConsoleSessions(null)); + } + } + + @Test + public void getBaseDomainIdToListConsoleSessionsTestPerformAccessValidationWhenDomainIsProvided() { + long domainId = 5L; + + try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { + callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); + Mockito.when(callContextMock.getCallingAccount()).thenReturn(account); + Mockito.when(domainDaoMock.findById(domainId)).thenReturn(domainMock); + Assert.assertEquals(domainId, consoleAccessManager.getBaseDomainIdToListConsoleSessions(domainId)); + Mockito.verify(accountManager).checkAccess(account, domainMock); + } + } + @Test public void listConsoleSessionByIdTestShouldCallDbLayer() { consoleAccessManager.listConsoleSessionById(1L); From 044d37b361c22f89f2fef1cb172f1e793b09fb80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Thu, 12 Jun 2025 09:36:40 -0300 Subject: [PATCH 05/18] deal with inexistent provided domain ID --- .../consoleproxy/ConsoleAccessManagerImpl.java | 9 ++++++++- .../consoleproxy/ConsoleAccessManagerImplTest.java | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java index dba9c64e8da8..5dcaa4d112e5 100644 --- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java @@ -28,7 +28,9 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.domain.Domain; import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.InvalidParameterValueException; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint; @@ -255,7 +257,12 @@ protected long getBaseDomainIdToListConsoleSessions(Long domainId) { return caller.getDomainId(); } - accountManager.checkAccess(caller, domainDao.findById(domainId)); + Domain domain = domainDao.findById(domainId); + if (domain == null) { + throw new InvalidParameterValueException(String.format("Unable to find domain with ID [%s]. Verify the informed domain and try again.", domainId)); + } + + accountManager.checkAccess(caller, domain); return domainId; } diff --git a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java index d45ab0e47f92..52ff7f53b041 100644 --- a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java @@ -172,6 +172,7 @@ public void listConsoleSessionsInternalTestAdminsShouldBeAllowedToRetrieveOtherA try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(callerDomainId); + Mockito.doReturn(callerDomainId).when(consoleAccessManager).getBaseDomainIdToListConsoleSessions(callerDomainId); Mockito.when(listConsoleSessionsCmdMock.getAccountId()).thenReturn(callerAccountId); Mockito.when(listConsoleSessionsCmdMock.getUserId()).thenReturn(callerUserId); Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive); @@ -199,6 +200,7 @@ public void listConsoleSessionsInternalTestShouldNotFetchConsoleSessionsRecursiv try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(callerDomainId); + Mockito.doReturn(callerDomainId).when(consoleAccessManager).getBaseDomainIdToListConsoleSessions(callerDomainId); Mockito.when(listConsoleSessionsCmdMock.getAccountId()).thenReturn(callerAccountId); Mockito.when(listConsoleSessionsCmdMock.getUserId()).thenReturn(callerUserId); Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive); @@ -226,6 +228,7 @@ public void listConsoleSessionsInternalTestShouldFetchConsoleSessionsRecursively try (MockedStatic callContextStaticMock = Mockito.mockStatic(CallContext.class)) { callContextStaticMock.when(CallContext::current).thenReturn(callContextMock); Mockito.when(listConsoleSessionsCmdMock.getDomainId()).thenReturn(callerDomainId); + Mockito.doReturn(callerDomainId).when(consoleAccessManager).getBaseDomainIdToListConsoleSessions(callerDomainId); Mockito.when(listConsoleSessionsCmdMock.getAccountId()).thenReturn(callerAccountId); Mockito.when(listConsoleSessionsCmdMock.getUserId()).thenReturn(callerUserId); Mockito.when(listConsoleSessionsCmdMock.isRecursive()).thenReturn(isRecursive); From aebf9a967636326bbaeb487b8ead6182ef0e7689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Thu, 12 Jun 2025 15:02:15 -0300 Subject: [PATCH 06/18] add end of line to ListConsoleSessionsCmd.java --- .../api/command/user/consoleproxy/ListConsoleSessionsCmd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java index b73e66733609..d60cab5fae04 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -169,4 +169,4 @@ public long getEntityOwnerId() { return Account.ACCOUNT_ID_SYSTEM; } -} \ No newline at end of file +} From a42decbcf89b66515c02c88c21c405a29a5d2145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Tue, 24 Jun 2025 09:01:27 -0300 Subject: [PATCH 07/18] enhance access control validation --- .../api/command/user/consoleproxy/ListConsoleSessionsCmd.java | 1 + .../cloudstack/consoleproxy/ConsoleAccessManagerImpl.java | 2 +- .../cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java index d60cab5fae04..3b37098a6190 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -50,6 +50,7 @@ public class ListConsoleSessionsCmd extends BaseListCmd { @Inject private ConsoleAccessManager consoleAccessManager; + @ACL @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ConsoleSessionResponse.class, description = "The ID of the console session.") private Long id; diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java index 5dcaa4d112e5..1c9c423b885b 100644 --- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java @@ -268,7 +268,7 @@ protected long getBaseDomainIdToListConsoleSessions(Long domainId) { @Override public ConsoleSession listConsoleSessionById(long id) { - return consoleSessionDao.findById(id); + return consoleSessionDao.findByIdIncludingRemoved(id); } @Override diff --git a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java index 52ff7f53b041..e929ce911cc5 100644 --- a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java @@ -309,6 +309,6 @@ public void getBaseDomainIdToListConsoleSessionsTestPerformAccessValidationWhenD @Test public void listConsoleSessionByIdTestShouldCallDbLayer() { consoleAccessManager.listConsoleSessionById(1L); - Mockito.verify(consoleSessionDaoMock).findById(1L); + Mockito.verify(consoleSessionDaoMock).findByIdIncludingRemoved(1L); } } From b6d519d0a626414e08357d2ba927fd6265ece562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Tue, 24 Jun 2025 10:55:31 -0300 Subject: [PATCH 08/18] Update api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java Co-authored-by: Nicolas Vazquez --- .../api/command/user/consoleproxy/ListConsoleSessionsCmd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java index 3b37098a6190..bbc54e610360 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -89,7 +89,7 @@ public class ListConsoleSessionsCmd extends BaseListCmd { description = "Lists only active console sessions, defaults to true. Active sessions are the ones that have been acquired and have not been removed.") private boolean activeOnly = true; - @Parameter(name = ApiConstants.IS_RECURSIVE, type = CommandType.BOOLEAN, since = "4.20.0.1-scclouds", + @Parameter(name = ApiConstants.IS_RECURSIVE, type = CommandType.BOOLEAN, description = "Lists console sessions recursively per domain. If an account ID is informed, only the account's console sessions will be listed. Defaults to false.") private boolean recursive = false; From a4531ad0bf4798ba298855723197d656d661f4a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Tue, 24 Jun 2025 10:56:07 -0300 Subject: [PATCH 09/18] Update server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java Co-authored-by: Nicolas Vazquez --- .../cloudstack/consoleproxy/ConsoleAccessManagerImpl.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java index 1c9c423b885b..63f3b9f10ab2 100644 --- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java @@ -228,12 +228,7 @@ protected Pair, Integer> listConsoleSessionsInternal(List userId = caller.getCallingUserId(); } - List domainIds; - if (isRecursive) { - domainIds = domainDao.getDomainAndChildrenIds(domainId); - } else { - domainIds = List.of(domainId); - } + List domainIds = isRecursive ? domainDao.getDomainAndChildrenIds(domainId) : List.of(domainId); return consoleSessionDao.listConsoleSessions(cmd.getId(), domainIds, accountId, userId, cmd.getHostId(), cmd.getStartDate(), cmd.getEndDate(), cmd.getInstanceId(), From feab0c8f93f466d4e4920eb9817d4c57d4f0b187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Tue, 24 Jun 2025 19:15:14 -0300 Subject: [PATCH 10/18] refactor createConsoleSessionResponse method --- .../java/com/cloud/api/ApiResponseHelper.java | 58 +++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 23f7ae9167e6..7c748bc2847d 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -5617,15 +5617,10 @@ public GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin) { return guiThemeResponse; } - @Override - public ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consoleSession, ResponseView responseView) { - ConsoleSessionResponse consoleSessionResponse = new ConsoleSessionResponse(); - consoleSessionResponse.setId(consoleSession.getUuid()); - consoleSessionResponse.setCreated(consoleSession.getCreated()); - consoleSessionResponse.setAcquired(consoleSession.getAcquired()); - consoleSessionResponse.setRemoved(consoleSession.getRemoved()); - consoleSessionResponse.setConsoleEndpointCreatorAddress(consoleSession.getConsoleEndpointCreatorAddress()); - consoleSessionResponse.setClientAddress(consoleSession.getClientAddress()); + private void populateDomainFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { + if (consoleSession == null) { + return; + } Domain domain = ApiDBUtils.findDomainById(consoleSession.getDomainId()); if (domain != null) { @@ -5633,29 +5628,72 @@ public ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consol consoleSessionResponse.setDomainPath(domain.getPath()); consoleSessionResponse.setDomainId(domain.getUuid()); } + } + + private void populateUserFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { + if (consoleSession == null) { + return; + } User user = findUserById(consoleSession.getUserId()); if (user != null) { consoleSessionResponse.setUser(user.getUsername()); consoleSessionResponse.setUserId(user.getUuid()); } + } + + private void populateAccountFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { + if (consoleSession == null) { + return; + } Account account = ApiDBUtils.findAccountById(consoleSession.getAccountId()); if (account != null) { consoleSessionResponse.setAccount(account.getAccountName()); consoleSessionResponse.setAccountId(account.getUuid()); } + } + + private void populateHostFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { + if (consoleSession == null) { + return; + } Host host = findHostById(consoleSession.getHostId()); - if (responseView == ResponseView.Full && host != null) { + if (host != null) { consoleSessionResponse.setHostId(host.getUuid()); consoleSessionResponse.setHostName(host.getName()); } + } + + private void populateInstanceFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { + if (consoleSession == null) { + return; + } VMInstanceVO instance = ApiDBUtils.findVMInstanceById(consoleSession.getInstanceId()); if (instance != null) { consoleSessionResponse.setInstanceId(instance.getUuid()); } + } + + @Override + public ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consoleSession, ResponseView responseView) { + ConsoleSessionResponse consoleSessionResponse = new ConsoleSessionResponse(); + consoleSessionResponse.setId(consoleSession.getUuid()); + consoleSessionResponse.setCreated(consoleSession.getCreated()); + consoleSessionResponse.setAcquired(consoleSession.getAcquired()); + consoleSessionResponse.setRemoved(consoleSession.getRemoved()); + consoleSessionResponse.setConsoleEndpointCreatorAddress(consoleSession.getConsoleEndpointCreatorAddress()); + consoleSessionResponse.setClientAddress(consoleSession.getClientAddress()); + + populateDomainFieldsOnConsoleSessionResponse(consoleSession, consoleSessionResponse); + populateUserFieldsOnConsoleSessionResponse(consoleSession, consoleSessionResponse); + populateAccountFieldsOnConsoleSessionResponse(consoleSession, consoleSessionResponse); + populateInstanceFieldsOnConsoleSessionResponse(consoleSession, consoleSessionResponse); + if (responseView == ResponseView.Full) { + populateHostFieldsOnConsoleSessionResponse(consoleSession, consoleSessionResponse); + } consoleSessionResponse.setObjectName("consolesession"); return consoleSessionResponse; From c9424ff2b153a3acdaba8520ca9b6c55520aa118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Thu, 26 Jun 2025 13:07:01 -0300 Subject: [PATCH 11/18] add instancename to the APIs response --- .../api/response/ConsoleSessionResponse.java | 12 ++++++++++++ .../main/java/com/cloud/api/ApiResponseHelper.java | 1 + .../java/com/cloud/api/ApiResponseHelperTest.java | 3 +++ 3 files changed, 16 insertions(+) diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java index d5bc72f8c480..fd074fe7708a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java @@ -70,6 +70,10 @@ public class ConsoleSessionResponse extends BaseResponse { @Param(description = "ID of the instance.") private String instanceId; + @SerializedName(ApiConstants.INSTANCE_NAME) + @Param(description = "Name of the instance.") + private String instanceName; + @SerializedName(ApiConstants.HOST_ID) @Param(description = "ID of the host.") private String hostId; @@ -134,6 +138,10 @@ public void setInstanceId(String instanceId) { this.instanceId = instanceId; } + public void setInstanceName(String instanceName) { + this.instanceName = instanceName; + } + public void setHostId(String hostId) { this.hostId = hostId; } @@ -198,6 +206,10 @@ public String getInstanceId() { return instanceId; } + public String getInstanceName() { + return instanceName; + } + public String getHostId() { return hostId; } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 7c748bc2847d..986e7da72a6e 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -5674,6 +5674,7 @@ private void populateInstanceFieldsOnConsoleSessionResponse(ConsoleSession conso VMInstanceVO instance = ApiDBUtils.findVMInstanceById(consoleSession.getInstanceId()); if (instance != null) { consoleSessionResponse.setInstanceId(instance.getUuid()); + consoleSessionResponse.setInstanceName(instance.getInstanceName()); } } diff --git a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java index 74c8e4872658..f59e41b10a7d 100644 --- a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java +++ b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java @@ -670,6 +670,7 @@ private ConsoleSessionResponse getExpectedConsoleSessionResponseForTests(boolean expected.setHostName("host"); expected.setHostId("hostUuid"); expected.setInstanceId("vmUuid"); + expected.setInstanceName("vmName"); } return expected; @@ -735,6 +736,7 @@ public void createConsoleSessionResponseTestShouldReturnFullResponse() { apiDBUtilsStaticMock.when(() -> ApiDBUtils.findVMInstanceById(2L)).thenReturn(vmInstanceVOMock); Mockito.when(vmInstanceVOMock.getUuid()).thenReturn(expected.getInstanceId()); + Mockito.when(vmInstanceVOMock.getInstanceName()).thenReturn(expected.getInstanceName()); ConsoleSessionResponse response = apiResponseHelper.createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Full); @@ -754,6 +756,7 @@ public void createConsoleSessionResponseTestShouldReturnFullResponse() { Assert.assertEquals(expected.getHostId(), response.getHostId()); Assert.assertEquals(expected.getHostName(), response.getHostName()); Assert.assertEquals(expected.getInstanceId(), response.getInstanceId()); + Assert.assertEquals(expected.getInstanceName(), response.getInstanceName()); } } } From 7f2ec61008aa7d7e1a3a08551bf6567dbe4c8f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Wed, 2 Jul 2025 10:03:43 -0300 Subject: [PATCH 12/18] add constants to ConsoleSessionDaoImpl --- .../cloud/vm/dao/ConsoleSessionDaoImpl.java | 71 +++++++++++-------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java index 28d875a7f0b8..151bc8e11f24 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java @@ -32,13 +32,28 @@ import com.cloud.vm.ConsoleSessionVO; public class ConsoleSessionDaoImpl extends GenericDaoBase implements ConsoleSessionDao { + private static final String ID = "id"; + private static final String DOMAIN_IDS = "domainIds"; + private static final String ACCOUNT_ID = "accountId"; + private static final String USER_ID = "userId"; + private static final String HOST_ID = "hostId"; + private static final String INSTANCE_ID = "instanceId"; + private static final String VM_IDS = "vmIds"; + private static final String START_DATE = "startDate"; + private static final String END_DATE = "endDate"; + private static final String CREATOR_ADDRESS = "creatorAddress"; + private static final String CLIENT_ADDRESS = "clientAddress"; + private static final String ACQUIRED = "acquired"; + private static final String CREATED = "created"; + private static final String REMOVED = "removed"; + private static final String REMOVED_NOT_NULL = "removedNotNull"; private final SearchBuilder searchByRemovedDate; public ConsoleSessionDaoImpl() { searchByRemovedDate = createSearchBuilder(); - searchByRemovedDate.and("removedNotNull", searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.NNULL); - searchByRemovedDate.and("removed", searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.LTEQ); + searchByRemovedDate.and(REMOVED_NOT_NULL, searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.NNULL); + searchByRemovedDate.and(REMOVED, searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.LTEQ); } @Override @@ -59,7 +74,7 @@ public boolean isSessionAllowed(String sessionUuid) { @Override public int expungeSessionsOlderThanDate(Date date) { SearchCriteria searchCriteria = searchByRemovedDate.create(); - searchCriteria.setParameters("removed", date); + searchCriteria.setParameters(REMOVED, date); return expunge(searchCriteria); } @@ -77,9 +92,9 @@ public int expungeByVmList(List vmIds, Long batchSize) { return 0; } SearchBuilder sb = createSearchBuilder(); - sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + sb.and(VM_IDS, sb.entity().getInstanceId(), SearchCriteria.Op.IN); SearchCriteria sc = sb.create(); - sc.setParameters("vmIds", vmIds.toArray()); + sc.setParameters(VM_IDS, vmIds.toArray()); return batchExpunge(sc, batchSize); } @@ -88,7 +103,7 @@ public Pair, Integer> listConsoleSessions(Long id, List searchCriteria = createListConsoleSessionsSearchCriteria(id, domainIds, accountId, userId, hostId, startDate, endDate, instanceId, consoleEndpointCreatorAddress, clientAddress, activeOnly); @@ -101,16 +116,16 @@ private SearchCriteria createListConsoleSessionsSearchCriteria boolean activeOnly) { SearchCriteria searchCriteria = createListConsoleSessionsSearchBuilder(activeOnly).create(); - searchCriteria.setParametersIfNotNull("id", id); - searchCriteria.setParametersIfNotNull("domainIds", domainIds.toArray()); - searchCriteria.setParametersIfNotNull("accountId", accountId); - searchCriteria.setParametersIfNotNull("userId", userId); - searchCriteria.setParametersIfNotNull("hostId", hostId); - searchCriteria.setParametersIfNotNull("instanceId", instanceId); - searchCriteria.setParametersIfNotNull("startDate", startDate); - searchCriteria.setParametersIfNotNull("endDate", endDate); - searchCriteria.setParametersIfNotNull("creatorAddress", consoleEndpointCreatorAddress); - searchCriteria.setParametersIfNotNull("clientAddress", clientAddress); + searchCriteria.setParametersIfNotNull(ID, id); + searchCriteria.setParametersIfNotNull(DOMAIN_IDS, domainIds.toArray()); + searchCriteria.setParametersIfNotNull(ACCOUNT_ID, accountId); + searchCriteria.setParametersIfNotNull(USER_ID, userId); + searchCriteria.setParametersIfNotNull(HOST_ID, hostId); + searchCriteria.setParametersIfNotNull(INSTANCE_ID, instanceId); + searchCriteria.setParametersIfNotNull(START_DATE, startDate); + searchCriteria.setParametersIfNotNull(END_DATE, endDate); + searchCriteria.setParametersIfNotNull(CREATOR_ADDRESS, consoleEndpointCreatorAddress); + searchCriteria.setParametersIfNotNull(CLIENT_ADDRESS, clientAddress); return searchCriteria; } @@ -118,20 +133,20 @@ private SearchCriteria createListConsoleSessionsSearchCriteria private SearchBuilder createListConsoleSessionsSearchBuilder(boolean activeOnly) { SearchBuilder searchBuilder = createSearchBuilder(); - searchBuilder.and("id", searchBuilder.entity().getId(), SearchCriteria.Op.EQ); - searchBuilder.and("domainIds", searchBuilder.entity().getDomainId(), SearchCriteria.Op.IN); - searchBuilder.and("accountId", searchBuilder.entity().getAccountId(), SearchCriteria.Op.EQ); - searchBuilder.and("userId", searchBuilder.entity().getUserId(), SearchCriteria.Op.EQ); - searchBuilder.and("hostId", searchBuilder.entity().getHostId(), SearchCriteria.Op.EQ); - searchBuilder.and("instanceId", searchBuilder.entity().getInstanceId(), SearchCriteria.Op.EQ); - searchBuilder.and("startDate", searchBuilder.entity().getAcquired(), SearchCriteria.Op.GTEQ); - searchBuilder.and("endDate", searchBuilder.entity().getAcquired(), SearchCriteria.Op.LTEQ); - searchBuilder.and("creatorAddress", searchBuilder.entity().getConsoleEndpointCreatorAddress(), SearchCriteria.Op.EQ); - searchBuilder.and("clientAddress", searchBuilder.entity().getClientAddress(), SearchCriteria.Op.EQ); + searchBuilder.and(ID, searchBuilder.entity().getId(), SearchCriteria.Op.EQ); + searchBuilder.and(DOMAIN_IDS, searchBuilder.entity().getDomainId(), SearchCriteria.Op.IN); + searchBuilder.and(ACCOUNT_ID, searchBuilder.entity().getAccountId(), SearchCriteria.Op.EQ); + searchBuilder.and(USER_ID, searchBuilder.entity().getUserId(), SearchCriteria.Op.EQ); + searchBuilder.and(HOST_ID, searchBuilder.entity().getHostId(), SearchCriteria.Op.EQ); + searchBuilder.and(INSTANCE_ID, searchBuilder.entity().getInstanceId(), SearchCriteria.Op.EQ); + searchBuilder.and(START_DATE, searchBuilder.entity().getAcquired(), SearchCriteria.Op.GTEQ); + searchBuilder.and(END_DATE, searchBuilder.entity().getAcquired(), SearchCriteria.Op.LTEQ); + searchBuilder.and(CREATOR_ADDRESS, searchBuilder.entity().getConsoleEndpointCreatorAddress(), SearchCriteria.Op.EQ); + searchBuilder.and(CLIENT_ADDRESS, searchBuilder.entity().getClientAddress(), SearchCriteria.Op.EQ); if (activeOnly) { - searchBuilder.and("acquired", searchBuilder.entity().getAcquired(), SearchCriteria.Op.NNULL); - searchBuilder.and("removed", searchBuilder.entity().getRemoved(), SearchCriteria.Op.NULL); + searchBuilder.and(ACQUIRED, searchBuilder.entity().getAcquired(), SearchCriteria.Op.NNULL); + searchBuilder.and(REMOVED, searchBuilder.entity().getRemoved(), SearchCriteria.Op.NULL); } searchBuilder.done(); From b15254dc753f2dbdb332b85c1107e51449b1dd04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Wed, 2 Jul 2025 10:06:28 -0300 Subject: [PATCH 13/18] check if consoleSession is null at the beginning of the method --- .../java/com/cloud/api/ApiResponseHelper.java | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 986e7da72a6e..a2fb3dbdd918 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -5618,10 +5618,6 @@ public GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin) { } private void populateDomainFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { - if (consoleSession == null) { - return; - } - Domain domain = ApiDBUtils.findDomainById(consoleSession.getDomainId()); if (domain != null) { consoleSessionResponse.setDomain(domain.getName()); @@ -5631,10 +5627,6 @@ private void populateDomainFieldsOnConsoleSessionResponse(ConsoleSession console } private void populateUserFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { - if (consoleSession == null) { - return; - } - User user = findUserById(consoleSession.getUserId()); if (user != null) { consoleSessionResponse.setUser(user.getUsername()); @@ -5643,10 +5635,6 @@ private void populateUserFieldsOnConsoleSessionResponse(ConsoleSession consoleSe } private void populateAccountFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { - if (consoleSession == null) { - return; - } - Account account = ApiDBUtils.findAccountById(consoleSession.getAccountId()); if (account != null) { consoleSessionResponse.setAccount(account.getAccountName()); @@ -5655,10 +5643,6 @@ private void populateAccountFieldsOnConsoleSessionResponse(ConsoleSession consol } private void populateHostFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { - if (consoleSession == null) { - return; - } - Host host = findHostById(consoleSession.getHostId()); if (host != null) { consoleSessionResponse.setHostId(host.getUuid()); @@ -5667,10 +5651,6 @@ private void populateHostFieldsOnConsoleSessionResponse(ConsoleSession consoleSe } private void populateInstanceFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { - if (consoleSession == null) { - return; - } - VMInstanceVO instance = ApiDBUtils.findVMInstanceById(consoleSession.getInstanceId()); if (instance != null) { consoleSessionResponse.setInstanceId(instance.getUuid()); @@ -5681,6 +5661,10 @@ private void populateInstanceFieldsOnConsoleSessionResponse(ConsoleSession conso @Override public ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consoleSession, ResponseView responseView) { ConsoleSessionResponse consoleSessionResponse = new ConsoleSessionResponse(); + if (consoleSession == null) { + return consoleSessionResponse; + } + consoleSessionResponse.setId(consoleSession.getUuid()); consoleSessionResponse.setCreated(consoleSession.getCreated()); consoleSessionResponse.setAcquired(consoleSession.getAcquired()); From 896b4651aff054d92cb10a06a856aa43d4275224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Fri, 4 Jul 2025 09:01:09 -0300 Subject: [PATCH 14/18] compare startdate and enddate parameters with the created attribute --- .../api/command/user/consoleproxy/ListConsoleSessionsCmd.java | 4 ++-- .../src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java index bbc54e610360..504c8fd2d11f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -68,11 +68,11 @@ public class ListConsoleSessionsCmd extends BaseListCmd { @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, authorized = {RoleType.Admin}, description = "Lists console sessions from the specified host.") private Long hostId; - @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Lists console sessions acquired from this date onwards. " + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Lists console sessions generated from this date onwards. " + ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS) private Date startDate; - @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "Lists console sessions acquired up until this date. " + + @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "Lists console sessions generated up until this date. " + ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS) private Date endDate; diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java index 151bc8e11f24..ba4831f59917 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java @@ -139,8 +139,8 @@ private SearchBuilder createListConsoleSessionsSearchBuilder(b searchBuilder.and(USER_ID, searchBuilder.entity().getUserId(), SearchCriteria.Op.EQ); searchBuilder.and(HOST_ID, searchBuilder.entity().getHostId(), SearchCriteria.Op.EQ); searchBuilder.and(INSTANCE_ID, searchBuilder.entity().getInstanceId(), SearchCriteria.Op.EQ); - searchBuilder.and(START_DATE, searchBuilder.entity().getAcquired(), SearchCriteria.Op.GTEQ); - searchBuilder.and(END_DATE, searchBuilder.entity().getAcquired(), SearchCriteria.Op.LTEQ); + searchBuilder.and(START_DATE, searchBuilder.entity().getCreated(), SearchCriteria.Op.GTEQ); + searchBuilder.and(END_DATE, searchBuilder.entity().getCreated(), SearchCriteria.Op.LTEQ); searchBuilder.and(CREATOR_ADDRESS, searchBuilder.entity().getConsoleEndpointCreatorAddress(), SearchCriteria.Op.EQ); searchBuilder.and(CLIENT_ADDRESS, searchBuilder.entity().getClientAddress(), SearchCriteria.Op.EQ); From 5f15b87bc6c0a3f38f633c4018bcf31133ba4a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Fri, 4 Jul 2025 09:42:23 -0300 Subject: [PATCH 15/18] add acquired param --- .../user/consoleproxy/ListConsoleSessionsCmd.java | 9 +++++++++ .../java/com/cloud/vm/dao/ConsoleSessionDao.java | 2 +- .../java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java | 12 +++++++----- .../consoleproxy/ConsoleAccessManagerImpl.java | 2 +- .../consoleproxy/ConsoleAccessManagerImplTest.java | 8 ++++---- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java index 504c8fd2d11f..8c707538525e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -89,6 +89,11 @@ public class ListConsoleSessionsCmd extends BaseListCmd { description = "Lists only active console sessions, defaults to true. Active sessions are the ones that have been acquired and have not been removed.") private boolean activeOnly = true; + @Parameter(name = ApiConstants.ACQUIRED, type = CommandType.BOOLEAN, + description = "Lists acquired console sessions, defaults to false. Acquired console sessions are the ones that have been accessed. " + + "The 'activeonly' parameter has precedence over the 'acquired' parameter, i.e., when the 'activeonly' parameter is 'true', the 'acquired' parameter value will be ignored.") + private boolean acquired = false; + @Parameter(name = ApiConstants.IS_RECURSIVE, type = CommandType.BOOLEAN, description = "Lists console sessions recursively per domain. If an account ID is informed, only the account's console sessions will be listed. Defaults to false.") private boolean recursive = false; @@ -137,6 +142,10 @@ public boolean isActiveOnly() { return activeOnly; } + public boolean getAcquired() { + return acquired; + } + public boolean isRecursive() { return recursive; } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java index bd199fe2eb3b..b8fb9557a356 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java @@ -41,5 +41,5 @@ public interface ConsoleSessionDao extends GenericDao { Pair, Integer> listConsoleSessions(Long id, List domainIds, Long accountId, Long userId, Long hostId, Date startDate, Date endDate, Long instanceId, String consoleEndpointCreatorAddress, String clientAddress, - boolean activeOnly, Long pageSizeVal, Long startIndex); + boolean activeOnly, boolean acquired, Long pageSizeVal, Long startIndex); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java index ba4831f59917..562142eecc81 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java @@ -102,10 +102,10 @@ public int expungeByVmList(List vmIds, Long batchSize) { public Pair, Integer> listConsoleSessions(Long id, List domainIds, Long accountId, Long userId, Long hostId, Date startDate, Date endDate, Long instanceId, String consoleEndpointCreatorAddress, String clientAddress, - boolean activeOnly, Long pageSizeVal, Long startIndex) { + boolean activeOnly, boolean acquired, Long pageSizeVal, Long startIndex) { Filter filter = new Filter(ConsoleSessionVO.class, CREATED, false, startIndex, pageSizeVal); SearchCriteria searchCriteria = createListConsoleSessionsSearchCriteria(id, domainIds, accountId, userId, hostId, - startDate, endDate, instanceId, consoleEndpointCreatorAddress, clientAddress, activeOnly); + startDate, endDate, instanceId, consoleEndpointCreatorAddress, clientAddress, activeOnly, acquired); return searchAndCount(searchCriteria, filter, true); } @@ -113,8 +113,8 @@ public Pair, Integer> listConsoleSessions(Long id, List createListConsoleSessionsSearchCriteria(Long id, List domainIds, Long accountId, Long userId, Long hostId, Date startDate, Date endDate, Long instanceId, String consoleEndpointCreatorAddress, String clientAddress, - boolean activeOnly) { - SearchCriteria searchCriteria = createListConsoleSessionsSearchBuilder(activeOnly).create(); + boolean activeOnly, boolean acquired) { + SearchCriteria searchCriteria = createListConsoleSessionsSearchBuilder(activeOnly, acquired).create(); searchCriteria.setParametersIfNotNull(ID, id); searchCriteria.setParametersIfNotNull(DOMAIN_IDS, domainIds.toArray()); @@ -130,7 +130,7 @@ private SearchCriteria createListConsoleSessionsSearchCriteria return searchCriteria; } - private SearchBuilder createListConsoleSessionsSearchBuilder(boolean activeOnly) { + private SearchBuilder createListConsoleSessionsSearchBuilder(boolean activeOnly, boolean acquired) { SearchBuilder searchBuilder = createSearchBuilder(); searchBuilder.and(ID, searchBuilder.entity().getId(), SearchCriteria.Op.EQ); @@ -147,6 +147,8 @@ private SearchBuilder createListConsoleSessionsSearchBuilder(b if (activeOnly) { searchBuilder.and(ACQUIRED, searchBuilder.entity().getAcquired(), SearchCriteria.Op.NNULL); searchBuilder.and(REMOVED, searchBuilder.entity().getRemoved(), SearchCriteria.Op.NULL); + } else if (acquired) { + searchBuilder.and(ACQUIRED, searchBuilder.entity().getAcquired(), SearchCriteria.Op.NNULL); } searchBuilder.done(); diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java index 63f3b9f10ab2..873c19b49ae3 100644 --- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java @@ -233,7 +233,7 @@ protected Pair, Integer> listConsoleSessionsInternal(List return consoleSessionDao.listConsoleSessions(cmd.getId(), domainIds, accountId, userId, cmd.getHostId(), cmd.getStartDate(), cmd.getEndDate(), cmd.getInstanceId(), cmd.getConsoleEndpointCreatorAddress(), cmd.getClientAddress(), cmd.isActiveOnly(), - cmd.getPageSizeVal(), cmd.getStartIndex()); + cmd.getAcquired(), cmd.getPageSizeVal(), cmd.getStartIndex()); } /** diff --git a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java index e929ce911cc5..ec7ef20d441e 100644 --- a/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImplTest.java @@ -158,7 +158,7 @@ public void listConsoleSessionsInternalTestNormalUsersShouldOnlyBeAllowedToListT Mockito.verify(consoleSessionDaoMock).listConsoleSessions( Mockito.any(), Mockito.eq(List.of(callerDomainId)), Mockito.eq(callerAccountId), Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), - Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() + Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() ); } @@ -185,7 +185,7 @@ public void listConsoleSessionsInternalTestAdminsShouldBeAllowedToRetrieveOtherA Mockito.verify(consoleSessionDaoMock).listConsoleSessions( Mockito.any(), Mockito.eq(List.of(callerDomainId)), Mockito.eq(callerAccountId), Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), - Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() + Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() ); } @@ -213,7 +213,7 @@ public void listConsoleSessionsInternalTestShouldNotFetchConsoleSessionsRecursiv Mockito.verify(consoleSessionDaoMock).listConsoleSessions( Mockito.any(), Mockito.eq(List.of(callerDomainId)), Mockito.eq(callerAccountId), Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), - Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() + Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() ); } @@ -242,7 +242,7 @@ public void listConsoleSessionsInternalTestShouldFetchConsoleSessionsRecursively Mockito.verify(consoleSessionDaoMock).listConsoleSessions( Mockito.any(), Mockito.eq(domainIdsCallerHasAccessTo), Mockito.eq(callerAccountId), Mockito.eq(callerUserId), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), - Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() + Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.any(), Mockito.any() ); } From b01e767ba073d0b3c7613d735769452fe18799bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Wed, 9 Jul 2025 09:40:14 -0300 Subject: [PATCH 16/18] rename instanceid param --- api/src/main/java/org/apache/cloudstack/api/ApiConstants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 660b4e8a6e8b..d25abbab4375 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -276,7 +276,7 @@ public class ApiConstants { public static final String HYPERVISOR = "hypervisor"; public static final String INLINE = "inline"; public static final String INSTANCE = "instance"; - public static final String INSTANCE_ID = "instance_id"; + public static final String INSTANCE_ID = "instanceid"; public static final String ICMP_CODE = "icmpcode"; public static final String ICMP_TYPE = "icmptype"; public static final String ID = "id"; From 78a5f91e483438f0bc47bef839d52f69e5eadfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Tue, 29 Jul 2025 09:37:58 -0300 Subject: [PATCH 17/18] rename instanceid parameter --- .../consoleproxy/ListConsoleSessionsCmd.java | 8 +++--- .../api/response/ConsoleSessionResponse.java | 28 +++++++++---------- .../java/com/cloud/api/ApiResponseHelper.java | 4 +-- .../ConsoleAccessManagerImpl.java | 2 +- .../com/cloud/api/ApiResponseHelperTest.java | 12 ++++---- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java index 8c707538525e..774cd9d59fe7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -76,8 +76,8 @@ public class ListConsoleSessionsCmd extends BaseListCmd { ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS) private Date endDate; - @Parameter(name = ApiConstants.INSTANCE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, description = "The ID of the instance.") - private Long instanceId; + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, description = "The ID of the virtual machine.") + private Long vmId; @Parameter(name = ApiConstants.CONSOLE_ENDPOINT_CREATOR_ADDRESS, type = CommandType.STRING, description = "IP address of the creator of the console endpoint.") private String consoleEndpointCreatorAddress; @@ -126,8 +126,8 @@ public Date getEndDate() { return endDate; } - public Long getInstanceId() { - return instanceId; + public Long getVmId() { + return vmId; } public String getConsoleEndpointCreatorAddress() { diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java index fd074fe7708a..85747d7c2a8f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java @@ -66,13 +66,13 @@ public class ConsoleSessionResponse extends BaseResponse { @Param(description = "ID of the user that created the console endpoint.") private String userId; - @SerializedName(ApiConstants.INSTANCE_ID) - @Param(description = "ID of the instance.") - private String instanceId; + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "ID of the virtual machine.") + private String vmId; - @SerializedName(ApiConstants.INSTANCE_NAME) - @Param(description = "Name of the instance.") - private String instanceName; + @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) + @Param(description = "Name of the virtual machine.") + private String vmName; @SerializedName(ApiConstants.HOST_ID) @Param(description = "ID of the host.") @@ -134,12 +134,12 @@ public void setUserId(String userId) { this.userId = userId; } - public void setInstanceId(String instanceId) { - this.instanceId = instanceId; + public void setVmId(String vmId) { + this.vmId = vmId; } - public void setInstanceName(String instanceName) { - this.instanceName = instanceName; + public void setVmName(String vmName) { + this.vmName = vmName; } public void setHostId(String hostId) { @@ -202,12 +202,12 @@ public String getUserId() { return userId; } - public String getInstanceId() { - return instanceId; + public String getVmId() { + return vmId; } - public String getInstanceName() { - return instanceName; + public String getVmName() { + return vmName; } public String getHostId() { diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index a2fb3dbdd918..7a11a83c1801 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -5653,8 +5653,8 @@ private void populateHostFieldsOnConsoleSessionResponse(ConsoleSession consoleSe private void populateInstanceFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { VMInstanceVO instance = ApiDBUtils.findVMInstanceById(consoleSession.getInstanceId()); if (instance != null) { - consoleSessionResponse.setInstanceId(instance.getUuid()); - consoleSessionResponse.setInstanceName(instance.getInstanceName()); + consoleSessionResponse.setVmId(instance.getUuid()); + consoleSessionResponse.setVmName(instance.getInstanceName()); } } diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java index 873c19b49ae3..306023a22638 100644 --- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java @@ -231,7 +231,7 @@ protected Pair, Integer> listConsoleSessionsInternal(List List domainIds = isRecursive ? domainDao.getDomainAndChildrenIds(domainId) : List.of(domainId); return consoleSessionDao.listConsoleSessions(cmd.getId(), domainIds, accountId, userId, - cmd.getHostId(), cmd.getStartDate(), cmd.getEndDate(), cmd.getInstanceId(), + cmd.getHostId(), cmd.getStartDate(), cmd.getEndDate(), cmd.getVmId(), cmd.getConsoleEndpointCreatorAddress(), cmd.getClientAddress(), cmd.isActiveOnly(), cmd.getAcquired(), cmd.getPageSizeVal(), cmd.getStartIndex()); } diff --git a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java index f59e41b10a7d..223b0740cf27 100644 --- a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java +++ b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java @@ -669,8 +669,8 @@ private ConsoleSessionResponse getExpectedConsoleSessionResponseForTests(boolean expected.setAccountId("accountUuid"); expected.setHostName("host"); expected.setHostId("hostUuid"); - expected.setInstanceId("vmUuid"); - expected.setInstanceName("vmName"); + expected.setVmId("vmUuid"); + expected.setVmName("vmName"); } return expected; @@ -735,8 +735,8 @@ public void createConsoleSessionResponseTestShouldReturnFullResponse() { Mockito.when(hostVOMock.getName()).thenReturn(expected.getHostName()); apiDBUtilsStaticMock.when(() -> ApiDBUtils.findVMInstanceById(2L)).thenReturn(vmInstanceVOMock); - Mockito.when(vmInstanceVOMock.getUuid()).thenReturn(expected.getInstanceId()); - Mockito.when(vmInstanceVOMock.getInstanceName()).thenReturn(expected.getInstanceName()); + Mockito.when(vmInstanceVOMock.getUuid()).thenReturn(expected.getVmId()); + Mockito.when(vmInstanceVOMock.getInstanceName()).thenReturn(expected.getVmName()); ConsoleSessionResponse response = apiResponseHelper.createConsoleSessionResponse(consoleSessionMock, ResponseObject.ResponseView.Full); @@ -755,8 +755,8 @@ public void createConsoleSessionResponseTestShouldReturnFullResponse() { Assert.assertEquals(expected.getAccountId(), response.getAccountId()); Assert.assertEquals(expected.getHostId(), response.getHostId()); Assert.assertEquals(expected.getHostName(), response.getHostName()); - Assert.assertEquals(expected.getInstanceId(), response.getInstanceId()); - Assert.assertEquals(expected.getInstanceName(), response.getInstanceName()); + Assert.assertEquals(expected.getVmId(), response.getVmId()); + Assert.assertEquals(expected.getVmName(), response.getVmName()); } } } From dcece2222565e2d09741a5aa8186fd823e6773a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20De=20Marco=20Gon=C3=A7alves?= Date: Tue, 29 Jul 2025 10:04:13 -0300 Subject: [PATCH 18/18] remove instanceid from api constants --- api/src/main/java/org/apache/cloudstack/api/ApiConstants.java | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index d25abbab4375..f77f84951904 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -276,7 +276,6 @@ public class ApiConstants { public static final String HYPERVISOR = "hypervisor"; public static final String INLINE = "inline"; public static final String INSTANCE = "instance"; - public static final String INSTANCE_ID = "instanceid"; public static final String ICMP_CODE = "icmpcode"; public static final String ICMP_TYPE = "icmptype"; public static final String ID = "id";