This is an automated email from the ASF dual-hosted git repository. sureshanaparti pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push: new 9de77e1cc13 API to list console sessions (#11016) 9de77e1cc13 is described below commit 9de77e1cc137c64b3f5b75f1c759171b8a6e7973 Author: Bernardo De Marco Gonçalves <bernardomg2...@gmail.com> AuthorDate: Fri Aug 1 11:58:33 2025 -0300 API to list console sessions (#11016) * create API to list console sessions --- .../org/apache/cloudstack/api/ApiConstants.java | 3 + .../apache/cloudstack/api/ResponseGenerator.java | 4 + .../user/consoleproxy/ListConsoleSessionsCmd.java | 182 ++++++++++++++++ .../api/response/ConsoleSessionResponse.java | 236 +++++++++++++++++++++ .../consoleproxy/ConsoleAccessManager.java | 7 + .../cloudstack/consoleproxy/ConsoleSession.java | 32 +-- .../consoleproxy/ListConsoleSessionsCmdTest.java | 124 +++++++++++ .../main/java/com/cloud/vm/ConsoleSessionVO.java | 25 ++- .../java/com/cloud/vm/dao/ConsoleSessionDao.java | 6 + .../com/cloud/vm/dao/ConsoleSessionDaoImpl.java | 84 +++++++- .../resources/META-INF/db/schema-42010to42100.sql | 9 + .../main/java/com/cloud/api/ApiResponseHelper.java | 69 ++++++ .../com/cloud/server/ManagementServerImpl.java | 5 + .../consoleproxy/ConsoleAccessManagerImpl.java | 92 +++++++- .../java/com/cloud/api/ApiResponseHelperTest.java | 128 +++++++++++ .../consoleproxy/ConsoleAccessManagerImplTest.java | 205 ++++++++++++++++++ tools/apidoc/gen_toc.py | 3 +- 17 files changed, 1192 insertions(+), 22 deletions(-) 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 70d3763b0be..f77f8495190 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"; 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 51918570078..8e92e877f5c 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.List; 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 @@ public interface ResponseGenerator { void updateTemplateIsoResponsesForIcons(List<TemplateResponse> 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 00000000000..774cd9d59fe --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -0,0 +1,182 @@ +// 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; + + @ACL + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ConsoleSessionResponse.class, description = "The ID of the console session.") + private Long id; + + @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 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 generated up until this date. " + + ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS) + private Date endDate; + + @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; + + @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.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; + + 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 getVmId() { + return vmId; + } + + public String getConsoleEndpointCreatorAddress() { + return consoleEndpointCreatorAddress; + } + + public String getClientAddress() { + return clientAddress; + } + + public boolean isActiveOnly() { + return activeOnly; + } + + public boolean getAcquired() { + return acquired; + } + + public boolean isRecursive() { + return recursive; + } + + @Override + public void execute() { + ListResponse<ConsoleSessionResponse> 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; + } +} 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 00000000000..85747d7c2a8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java @@ -0,0 +1,236 @@ +// 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.VIRTUAL_MACHINE_ID) + @Param(description = "ID of the virtual machine.") + private String vmId; + + @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.") + 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 setVmId(String vmId) { + this.vmId = vmId; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + 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 getVmId() { + return vmId; + } + + public String getVmName() { + return vmName; + } + + 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 23b571e7fae..655b8faf443 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 @@ package org.apache.cloudstack.consoleproxy; 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<ConsoleSessionResponse> listConsoleSessions(ListConsoleSessionsCmd cmd); + + ConsoleSession listConsoleSessionById(long id); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleSession.java similarity index 63% copy from engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java copy to api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleSession.java index 95ced889b3d..6cbdd31fd94 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java +++ b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleSession.java @@ -1,4 +1,3 @@ -// // 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 @@ -15,25 +14,32 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -// +package org.apache.cloudstack.consoleproxy; -package com.cloud.vm.dao; - -import com.cloud.vm.ConsoleSessionVO; -import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; import java.util.Date; -import java.util.List; -public interface ConsoleSessionDao extends GenericDao<ConsoleSessionVO, Long> { +public interface ConsoleSession extends InternalIdentity, Identity { + + Date getCreated(); + + long getDomainId(); + + long getAccountId(); + + long getUserId(); + + long getInstanceId(); - void removeSession(String sessionUuid); + long getHostId(); - boolean isSessionAllowed(String sessionUuid); + Date getRemoved(); - int expungeSessionsOlderThanDate(Date date); + Date getAcquired(); - void acquireSession(String sessionUuid, String clientAddress); + String getConsoleEndpointCreatorAddress(); - int expungeByVmList(List<Long> vmIds, Long batchSize); + 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 00000000000..47bef14bb61 --- /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 ef777be2de9..d8f2838dd47 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 @@ import javax.persistence.TemporalType; @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 class ConsoleSessionVO { this.uuid = uuid; } + @Override public Date getCreated() { return created; } @@ -94,6 +100,16 @@ public class ConsoleSessionVO { 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 class ConsoleSessionVO { this.accountId = accountId; } + @Override public long getUserId() { return userId; } @@ -110,6 +127,7 @@ public class ConsoleSessionVO { this.userId = userId; } + @Override public long getInstanceId() { return instanceId; } @@ -118,6 +136,7 @@ public class ConsoleSessionVO { this.instanceId = instanceId; } + @Override public long getHostId() { return hostId; } @@ -126,6 +145,7 @@ public class ConsoleSessionVO { this.hostId = hostId; } + @Override public Date getRemoved() { return removed; } @@ -134,6 +154,7 @@ public class ConsoleSessionVO { this.removed = removed; } + @Override public Date getAcquired() { return acquired; } @@ -142,6 +163,7 @@ public class ConsoleSessionVO { this.acquired = acquired; } + @Override public String getConsoleEndpointCreatorAddress() { return consoleEndpointCreatorAddress; } @@ -150,6 +172,7 @@ public class ConsoleSessionVO { 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 95ced889b3d..b8fb9557a35 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<ConsoleSessionVO, Long> { void acquireSession(String sessionUuid, String clientAddress); int expungeByVmList(List<Long> vmIds, Long batchSize); + + Pair<List<ConsoleSessionVO>, Integer> listConsoleSessions(Long id, List<Long> domainIds, Long accountId, Long userId, Long hostId, + Date startDate, Date endDate, Long instanceId, + String consoleEndpointCreatorAddress, String clientAddress, + 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 3d117894670..562142eecc8 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 @@ package com.cloud.vm.dao; 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; @@ -30,13 +32,28 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.vm.ConsoleSessionVO; public class ConsoleSessionDaoImpl extends GenericDaoBase<ConsoleSessionVO, Long> 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<ConsoleSessionVO> 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 @@ -57,7 +74,7 @@ public class ConsoleSessionDaoImpl extends GenericDaoBase<ConsoleSessionVO, Long @Override public int expungeSessionsOlderThanDate(Date date) { SearchCriteria<ConsoleSessionVO> searchCriteria = searchByRemovedDate.create(); - searchCriteria.setParameters("removed", date); + searchCriteria.setParameters(REMOVED, date); return expunge(searchCriteria); } @@ -75,9 +92,66 @@ public class ConsoleSessionDaoImpl extends GenericDaoBase<ConsoleSessionVO, Long return 0; } SearchBuilder<ConsoleSessionVO> sb = createSearchBuilder(); - sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + sb.and(VM_IDS, sb.entity().getInstanceId(), SearchCriteria.Op.IN); SearchCriteria<ConsoleSessionVO> sc = sb.create(); - sc.setParameters("vmIds", vmIds.toArray()); + sc.setParameters(VM_IDS, vmIds.toArray()); return batchExpunge(sc, batchSize); } + + @Override + public Pair<List<ConsoleSessionVO>, Integer> listConsoleSessions(Long id, List<Long> domainIds, Long accountId, Long userId, Long hostId, + Date startDate, Date endDate, Long instanceId, + String consoleEndpointCreatorAddress, String clientAddress, + boolean activeOnly, boolean acquired, Long pageSizeVal, Long startIndex) { + Filter filter = new Filter(ConsoleSessionVO.class, CREATED, false, startIndex, pageSizeVal); + SearchCriteria<ConsoleSessionVO> searchCriteria = createListConsoleSessionsSearchCriteria(id, domainIds, accountId, userId, hostId, + startDate, endDate, instanceId, consoleEndpointCreatorAddress, clientAddress, activeOnly, acquired); + + return searchAndCount(searchCriteria, filter, true); + } + + private SearchCriteria<ConsoleSessionVO> createListConsoleSessionsSearchCriteria(Long id, List<Long> domainIds, Long accountId, Long userId, Long hostId, + Date startDate, Date endDate, Long instanceId, + String consoleEndpointCreatorAddress, String clientAddress, + boolean activeOnly, boolean acquired) { + SearchCriteria<ConsoleSessionVO> searchCriteria = createListConsoleSessionsSearchBuilder(activeOnly, acquired).create(); + + 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; + } + + private SearchBuilder<ConsoleSessionVO> createListConsoleSessionsSearchBuilder(boolean activeOnly, boolean acquired) { + SearchBuilder<ConsoleSessionVO> searchBuilder = createSearchBuilder(); + + 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().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); + + 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(); + 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 4677c8bbd3f..6b2b27f3361 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 41433cb3e6f..7a11a83c180 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.ConditionResponse; 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.backup.dao.BackupRepositoryDao; 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,71 @@ protected Map<String, ResourceIcon> getResourceIconsUsingOsCategory(List<Templat return guiThemeResponse; } + + private void populateDomainFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { + Domain domain = ApiDBUtils.findDomainById(consoleSession.getDomainId()); + if (domain != null) { + consoleSessionResponse.setDomain(domain.getName()); + consoleSessionResponse.setDomainPath(domain.getPath()); + consoleSessionResponse.setDomainId(domain.getUuid()); + } + } + + private void populateUserFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { + User user = findUserById(consoleSession.getUserId()); + if (user != null) { + consoleSessionResponse.setUser(user.getUsername()); + consoleSessionResponse.setUserId(user.getUuid()); + } + } + + private void populateAccountFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { + Account account = ApiDBUtils.findAccountById(consoleSession.getAccountId()); + if (account != null) { + consoleSessionResponse.setAccount(account.getAccountName()); + consoleSessionResponse.setAccountId(account.getUuid()); + } + } + + private void populateHostFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { + Host host = findHostById(consoleSession.getHostId()); + if (host != null) { + consoleSessionResponse.setHostId(host.getUuid()); + consoleSessionResponse.setHostName(host.getName()); + } + } + + private void populateInstanceFieldsOnConsoleSessionResponse(ConsoleSession consoleSession, ConsoleSessionResponse consoleSessionResponse) { + VMInstanceVO instance = ApiDBUtils.findVMInstanceById(consoleSession.getInstanceId()); + if (instance != null) { + consoleSessionResponse.setVmId(instance.getUuid()); + consoleSessionResponse.setVmName(instance.getInstanceName()); + } + } + + @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()); + 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; + } } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 4cd16b42ae9..3245acfbbf2 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.ListBucketsCmd; 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 class ManagementServerImpl extends ManagerBase implements ManagementServe 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); 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 0ab81a4c1d6..306023a2263 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,15 @@ import javax.crypto.spec.SecretKeySpec; 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; +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 +95,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 +114,8 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce DataCenterDao dataCenterDao; @Inject private ConsoleSessionDao consoleSessionDao; + @Inject + private ResponseGenerator responseGenerator; private ScheduledExecutorService executorService = null; @@ -181,6 +194,78 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce } } + @Override + public ListResponse<ConsoleSessionResponse> listConsoleSessions(ListConsoleSessionsCmd cmd) { + Pair<List<ConsoleSessionVO>, Integer> consoleSessions = listConsoleSessionsInternal(cmd); + ListResponse<ConsoleSessionResponse> response = new ListResponse<>(); + + ResponseObject.ResponseView responseView = ResponseObject.ResponseView.Restricted; + Long callerId = CallContext.current().getCallingAccountId(); + if (accountManager.isRootAdmin(callerId)) { + responseView = ResponseObject.ResponseView.Full; + } + + List<ConsoleSessionResponse> 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<List<ConsoleSessionVO>, Integer> listConsoleSessionsInternal(ListConsoleSessionsCmd cmd) { + CallContext caller = CallContext.current(); + long domainId = getBaseDomainIdToListConsoleSessions(cmd.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<Long> domainIds = isRecursive ? domainDao.getDomainAndChildrenIds(domainId) : List.of(domainId); + + return consoleSessionDao.listConsoleSessions(cmd.getId(), domainIds, accountId, userId, + cmd.getHostId(), cmd.getStartDate(), cmd.getEndDate(), cmd.getVmId(), + cmd.getConsoleEndpointCreatorAddress(), cmd.getClientAddress(), cmd.isActiveOnly(), + cmd.getAcquired(), 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(); + } + + 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; + } + + @Override + public ConsoleSession listConsoleSessionById(long id) { + return consoleSessionDao.findByIdIncludingRemoved(id); + } + @Override public ConsoleEndpoint generateConsoleEndpoint(Long vmId, String extraSecurityToken, String clientAddress) { try { @@ -411,10 +496,13 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce } 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); diff --git a/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java b/server/src/test/java/com/cloud/api/ApiResponseHelperTest.java index a5fc791de99..223b0740cf2 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 org.springframework.test.util.ReflectionTestUtils; 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.UserDataVO; 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,114 @@ public class ApiResponseHelperTest { 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.setVmId("vmUuid"); + expected.setVmName("vmName"); + } + + return expected; + } + + @Test + public void createConsoleSessionResponseTestShouldReturnRestrictedResponse() { + ConsoleSessionResponse expected = getExpectedConsoleSessionResponseForTests(false); + + try (MockedStatic<ApiDBUtils> 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<ApiDBUtils> 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.getVmId()); + Mockito.when(vmInstanceVOMock.getInstanceName()).thenReturn(expected.getVmName()); + + 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.getVmId(), response.getVmId()); + Assert.assertEquals(expected.getVmName(), response.getVmName()); + } + } } 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 748fe19893a..ec7ef20d441 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,32 @@ 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; 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 +77,23 @@ public class ConsoleAccessManagerImplTest { @Mock Account account; + @Mock + private CallContext callContextMock; + @Mock + private DomainDao domainDaoMock; + @Mock + private DomainVO domainMock; + @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 +134,181 @@ public class ConsoleAccessManagerImplTest { Assert.assertFalse(consoleAccessManager.checkSessionPermission(virtualMachine, account)); } } + + @Test + public void listConsoleSessionsInternalTestNormalUsersShouldOnlyBeAllowedToListTheirOwnConsoleSessions() { + long callerDomainId = 5L; + long callerAccountId = 5L; + long callerUserId = 5L; + boolean isRecursive = false; + + try (MockedStatic<CallContext> 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.anyBoolean(), Mockito.any(), Mockito.any() + ); + } + + @Test + public void listConsoleSessionsInternalTestAdminsShouldBeAllowedToRetrieveOtherAccountsConsoleSessions() { + long callerDomainId = 5L; + long callerAccountId = 5L; + long callerUserId = 5L; + boolean isRecursive = false; + + try (MockedStatic<CallContext> 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); + 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.anyBoolean(), Mockito.any(), Mockito.any() + ); + } + + + @Test + public void listConsoleSessionsInternalTestShouldNotFetchConsoleSessionsRecursivelyWhenIsRecursiveIsFalse() { + long callerDomainId = 5L; + long callerAccountId = 5L; + long callerUserId = 5L; + boolean isRecursive = false; + + try (MockedStatic<CallContext> 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); + 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.anyBoolean(), Mockito.any(), Mockito.any() + ); + } + + @Test + public void listConsoleSessionsInternalTestShouldFetchConsoleSessionsRecursivelyWhenIsRecursiveIsTrue() { + long callerDomainId = 5L; + long callerAccountId = 5L; + long callerUserId = 5L; + boolean isRecursive = true; + List<Long> domainIdsCallerHasAccessTo = List.of(callerDomainId, 6L, 7L); + + try (MockedStatic<CallContext> 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); + 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.anyBoolean(), Mockito.any(), Mockito.any() + ); + } + + @Test + public void listConsoleSessionsTestShouldCreateResponsesWithFullViewForRootAdmins() { + Mockito.doReturn(new Pair<>(List.of(consoleSessionMock), 1)) + .when(consoleAccessManager) + .listConsoleSessionsInternal(listConsoleSessionsCmdMock); + + try (MockedStatic<CallContext> 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.doReturn(new Pair<>(List.of(consoleSessionMock), 1)) + .when(consoleAccessManager) + .listConsoleSessionsInternal(listConsoleSessionsCmdMock); + + try (MockedStatic<CallContext> 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 getBaseDomainIdToListConsoleSessionsTestIfNoDomainIdIsProvidedReturnCallersDomainId() { + long callerDomainId = 5L; + + try (MockedStatic<CallContext> 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<CallContext> 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); + Mockito.verify(consoleSessionDaoMock).findByIdIncludingRemoved(1L); + } } diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index bcaaca2e39a..ad72b4c7938 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -228,7 +228,8 @@ known_categories = { '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',