Add UUID field for LDAP configuration (#11462)

* Add UUID field for LDAP configuration

* move db changes to the lastest schema file

* Add ID param to list ldapConf API & delete ldapConf API

* fix ui test

* fix 1 ui test

* fix test

* fix api description

---------

Co-authored-by: dahn <daan@onecht.net>
This commit is contained in:
Pearl Dsilva 2025-10-01 08:43:22 -04:00 committed by GitHub
parent 7dd0d6e937
commit cd12fa5848
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 89 additions and 30 deletions

View File

@ -26,5 +26,11 @@ CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('router_health_check', 'check_result', '
-- Increase length of scripts_version column to 128 due to md5sum to sha512sum change
CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('cloud.domain_router', 'scripts_version', 'scripts_version', 'VARCHAR(128)');
-- Add uuid column to ldap_configuration table
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.ldap_configuration', 'uuid', 'VARCHAR(40) NOT NULL');
-- Populate uuid for existing rows where uuid is NULL or empty
UPDATE `cloud`.`ldap_configuration` SET uuid = UUID() WHERE uuid IS NULL OR uuid = '';
-- Add the column cross_zone_instance_creation to cloud.backup_repository. if enabled it means that new Instance can be created on all Zones from Backups on this Repository.
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_repository', 'cross_zone_instance_creation', 'TINYINT(1) DEFAULT NULL COMMENT ''Backup Repository can be used for disaster recovery on another zone''');

View File

@ -40,8 +40,10 @@ public class LdapDeleteConfigurationCmd extends BaseCmd {
@Inject
private LdapManager _ldapManager;
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, required = false, entityType = LdapConfigurationResponse.class, description = "ID of the LDAP configuration")
private Long id;
@Parameter(name = ApiConstants.HOST_NAME, type = CommandType.STRING, required = true, description = "Hostname")
@Parameter(name = ApiConstants.HOST_NAME, type = CommandType.STRING, description = "Hostname")
private String hostname;
@Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, required = false, description = "port")
@ -71,6 +73,10 @@ public class LdapDeleteConfigurationCmd extends BaseCmd {
return domainId;
}
public Long getId() {
return id;
}
@Override
public void execute() throws ServerApiException {
try {

View File

@ -53,6 +53,9 @@ public class LdapListConfigurationCmd extends BaseListCmd {
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "linked domain")
private Long domainId;
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = LdapConfigurationResponse.class, description = "list ldap configuration by ID; when passed, all other parameters are ignored")
private Long id;
@Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, description = "If set to true, "
+ " and no domainid specified, list all LDAP configurations irrespective of the linked domain", since = "4.13.2")
private Boolean listAll;
@ -120,6 +123,10 @@ public class LdapListConfigurationCmd extends BaseListCmd {
this.domainId = domainId;
}
public Long getId() {
return id;
}
public boolean listAll() {
return listAll != null && listAll;
}

View File

@ -23,10 +23,14 @@ import org.apache.cloudstack.api.BaseResponse;
import com.cloud.serializer.Param;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.ldap.LdapConfiguration;
import org.apache.cloudstack.ldap.LdapConfigurationVO;
@EntityReference(value = LdapConfiguration.class)
@EntityReference(value = LdapConfigurationVO.class)
public class LdapConfigurationResponse extends BaseResponse {
@SerializedName("id")
@Param(description = "the ID of the LDAP configuration")
private String id;
@SerializedName(ApiConstants.HOST_NAME)
@Param(description = "name of the host running the ldap server")
private String hostname;
@ -53,9 +57,18 @@ public class LdapConfigurationResponse extends BaseResponse {
setPort(port);
}
public LdapConfigurationResponse(final String hostname, final int port, final String domainId) {
public LdapConfigurationResponse(final String hostname, final int port, final String domainId, final String id) {
this(hostname, port);
setDomainId(domainId);
setId(id);
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getHostname() {

View File

@ -23,19 +23,25 @@ import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import java.util.UUID;
@Entity
@Table(name = "ldap_configuration")
public class LdapConfigurationVO implements InternalIdentity {
@Column(name = "hostname")
private String hostname;
public class LdapConfigurationVO implements Identity, InternalIdentity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "hostname")
private String hostname;
@Column(name = "uuid")
private String uuid;
@Column(name = "port")
private int port;
@ -43,12 +49,14 @@ public class LdapConfigurationVO implements InternalIdentity {
private Long domainId;
public LdapConfigurationVO() {
this.uuid = UUID.randomUUID().toString();
}
public LdapConfigurationVO(final String hostname, final int port, final Long domainId) {
this.hostname = hostname;
this.port = port;
this.domainId = domainId;
this.uuid = UUID.randomUUID().toString();
}
public String getHostname() {
@ -60,6 +68,10 @@ public class LdapConfigurationVO implements InternalIdentity {
return id;
}
public String getUuid() {
return uuid;
}
public int getPort() {
return port;
}

View File

@ -52,6 +52,7 @@ import org.apache.cloudstack.framework.messagebus.MessageSubscriber;
import org.apache.cloudstack.ldap.dao.LdapConfigurationDao;
import org.apache.cloudstack.ldap.dao.LdapTrustMapDao;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.cloud.domain.DomainVO;
@ -240,7 +241,7 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag
domainUuid = domain.getUuid();
}
}
return new LdapConfigurationResponse(configuration.getHostname(), configuration.getPort(), domainUuid);
return new LdapConfigurationResponse(configuration.getHostname(), configuration.getPort(), domainUuid, configuration.getUuid());
}
@Override
@ -257,6 +258,19 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag
@Override
public LdapConfigurationResponse deleteConfiguration(final LdapDeleteConfigurationCmd cmd) throws InvalidParameterValueException {
Long id = cmd.getId();
String hostname = cmd.getHostname();
if (id == null && StringUtils.isEmpty(hostname)) {
throw new InvalidParameterValueException("Either id or hostname must be specified");
}
if (id != null) {
final LdapConfigurationVO config = _ldapConfigurationDao.findById(cmd.getId());
if (config != null) {
_ldapConfigurationDao.remove(config.getId());
return createLdapConfigurationResponse(config);
}
throw new InvalidParameterValueException("Cannot find configuration with id " + id);
}
return deleteConfigurationInternal(cmd.getHostname(), cmd.getPort(), cmd.getDomainId());
}
@ -377,7 +391,8 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag
final int port = cmd.getPort();
final Long domainId = cmd.getDomainId();
final boolean listAll = cmd.listAll();
final Pair<List<LdapConfigurationVO>, Integer> result = _ldapConfigurationDao.searchConfigurations(hostname, port, domainId, listAll);
final Long id = cmd.getId();
final Pair<List<LdapConfigurationVO>, Integer> result = _ldapConfigurationDao.searchConfigurations(id, hostname, port, domainId, listAll);
return new Pair<List<? extends LdapConfigurationVO>, Integer>(result.first(), result.second());
}

View File

@ -41,5 +41,5 @@ public interface LdapConfigurationDao extends GenericDao<LdapConfigurationVO, Lo
Pair<List<LdapConfigurationVO>, Integer> searchConfigurations(String hostname, int port, Long domainId);
Pair<List<LdapConfigurationVO>, Integer> searchConfigurations(String hostname, int port, Long domainId, boolean listAll);
Pair<List<LdapConfigurationVO>, Integer> searchConfigurations(Long id, String hostname, int port, Long domainId, boolean listAll);
}

View File

@ -48,6 +48,7 @@ public class LdapConfigurationDaoImpl extends GenericDaoBase<LdapConfigurationVO
listGlobalConfigurationsSearch.done();
listDomainConfigurationsSearch = createSearchBuilder();
listDomainConfigurationsSearch.and("id", listDomainConfigurationsSearch.entity().getId(), SearchCriteria.Op.EQ);
listDomainConfigurationsSearch.and("hostname", listDomainConfigurationsSearch.entity().getHostname(), Op.EQ);
listDomainConfigurationsSearch.and("port", listDomainConfigurationsSearch.entity().getPort(), Op.EQ);
listDomainConfigurationsSearch.and("domain_id", listDomainConfigurationsSearch.entity().getDomainId(), Op.EQ);
@ -63,31 +64,35 @@ public class LdapConfigurationDaoImpl extends GenericDaoBase<LdapConfigurationVO
@Override
public LdapConfigurationVO find(String hostname, int port, Long domainId) {
SearchCriteria<LdapConfigurationVO> sc = getSearchCriteria(hostname, port, domainId, false);
SearchCriteria<LdapConfigurationVO> sc = getSearchCriteria(null, hostname, port, domainId, false);
return findOneBy(sc);
}
@Override
public LdapConfigurationVO find(String hostname, int port, Long domainId, boolean listAll) {
SearchCriteria<LdapConfigurationVO> sc = getSearchCriteria(hostname, port, domainId, listAll);
SearchCriteria<LdapConfigurationVO> sc = getSearchCriteria(null, hostname, port, domainId, listAll);
return findOneBy(sc);
}
@Override
public Pair<List<LdapConfigurationVO>, Integer> searchConfigurations(final String hostname, final int port, final Long domainId) {
SearchCriteria<LdapConfigurationVO> sc = getSearchCriteria(hostname, port, domainId, false);
SearchCriteria<LdapConfigurationVO> sc = getSearchCriteria(null, hostname, port, domainId, false);
return searchAndCount(sc, null);
}
@Override
public Pair<List<LdapConfigurationVO>, Integer> searchConfigurations(final String hostname, final int port, final Long domainId, final boolean listAll) {
SearchCriteria<LdapConfigurationVO> sc = getSearchCriteria(hostname, port, domainId, listAll);
public Pair<List<LdapConfigurationVO>, Integer> searchConfigurations(final Long id, final String hostname, final int port, final Long domainId, final boolean listAll) {
SearchCriteria<LdapConfigurationVO> sc = getSearchCriteria(id, hostname, port, domainId, listAll);
return searchAndCount(sc, null);
}
private SearchCriteria<LdapConfigurationVO> getSearchCriteria(String hostname, int port, Long domainId,boolean listAll) {
private SearchCriteria<LdapConfigurationVO> getSearchCriteria(Long id, String hostname, int port, Long domainId,boolean listAll) {
SearchCriteria<LdapConfigurationVO> sc;
if (domainId != null) {
if (id != null) {
// If id is present, ignore all other parameters
sc = listDomainConfigurationsSearch.create();
sc.setParameters("id", id);
} else if (domainId != null) {
// If domainid is present, ignore listall
sc = listDomainConfigurationsSearch.create();
sc.setParameters("domain_id", domainId);

View File

@ -41,7 +41,7 @@ export default {
permission: ['listLdapConfigurations'],
searchFilters: ['domainid', 'hostname', 'port'],
columns: ['hostname', 'port', 'domainid'],
details: ['hostname', 'port', 'domainid'],
details: ['id', 'hostname', 'port', 'domainid'],
actions: [
{
api: 'addLdapConfiguration',

View File

@ -1078,8 +1078,6 @@ export default {
}
if (this.$route.path.startsWith('/vmsnapshot/')) {
params.vmsnapshotid = this.$route.params.id
} else if (this.$route.path.startsWith('/ldapsetting/')) {
params.hostname = this.$route.params.id
}
if (this.$route.path.startsWith('/tungstenpolicy/')) {
params.policyuuid = this.$route.params.id
@ -1192,9 +1190,6 @@ export default {
this.items[idx][key] = func(this.items[idx])
}
}
if (this.$route.path.startsWith('/ldapsetting')) {
this.items[idx].id = this.items[idx].hostname
}
}
if (this.items.length > 0) {
if (!this.showAction || this.dataView) {

View File

@ -652,12 +652,12 @@ describe('Views > AutogenView.vue', () => {
testapinamecase1response: {
count: 0,
testapinamecase1: [{
id: 'test-id-1',
id: 'uuid1',
name: 'test-name-1'
}]
}
})
await router.push({ name: 'testRouter13', params: { id: 'test-id' } })
await router.push({ name: 'testRouter13', params: { id: 'uuid1' } })
await flushPromises()
expect(mockAxios).toHaveBeenCalled()
@ -668,8 +668,7 @@ describe('Views > AutogenView.vue', () => {
command: 'testApiNameCase1',
response: 'json',
listall: true,
id: 'test-id',
hostname: 'test-id',
id: 'uuid1',
page: 1,
pagesize: 20
})
@ -777,6 +776,7 @@ describe('Views > AutogenView.vue', () => {
testapinamecase1response: {
count: 1,
testapinamecase1: [{
id: 'uuid1',
name: 'test-name-value',
hostname: 'test-hostname-value'
}]
@ -786,13 +786,13 @@ describe('Views > AutogenView.vue', () => {
await flushPromises()
expect(wrapper.vm.items).toEqual([{
id: 'test-hostname-value',
id: 'uuid1',
name: 'test-name-value',
hostname: 'test-hostname-value',
key: 0
}])
expect(wrapper.vm.resource).toEqual({
id: 'test-hostname-value',
id: 'uuid1',
name: 'test-name-value',
hostname: 'test-hostname-value',
key: 0