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 -- 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)'); 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. -- 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'''); 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 @Inject
private LdapManager _ldapManager; 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; private String hostname;
@Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, required = false, description = "port") @Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, required = false, description = "port")
@ -71,6 +73,10 @@ public class LdapDeleteConfigurationCmd extends BaseCmd {
return domainId; return domainId;
} }
public Long getId() {
return id;
}
@Override @Override
public void execute() throws ServerApiException { public void execute() throws ServerApiException {
try { 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") @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "linked domain")
private Long domainId; 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, " @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") + " and no domainid specified, list all LDAP configurations irrespective of the linked domain", since = "4.13.2")
private Boolean listAll; private Boolean listAll;
@ -120,6 +123,10 @@ public class LdapListConfigurationCmd extends BaseListCmd {
this.domainId = domainId; this.domainId = domainId;
} }
public Long getId() {
return id;
}
public boolean listAll() { public boolean listAll() {
return listAll != null && listAll; return listAll != null && listAll;
} }

View File

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

View File

@ -23,19 +23,25 @@ import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Table; import javax.persistence.Table;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.api.InternalIdentity;
import java.util.UUID;
@Entity @Entity
@Table(name = "ldap_configuration") @Table(name = "ldap_configuration")
public class LdapConfigurationVO implements InternalIdentity { public class LdapConfigurationVO implements Identity, InternalIdentity {
@Column(name = "hostname")
private String hostname;
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id") @Column(name = "id")
private Long id; private Long id;
@Column(name = "hostname")
private String hostname;
@Column(name = "uuid")
private String uuid;
@Column(name = "port") @Column(name = "port")
private int port; private int port;
@ -43,12 +49,14 @@ public class LdapConfigurationVO implements InternalIdentity {
private Long domainId; private Long domainId;
public LdapConfigurationVO() { public LdapConfigurationVO() {
this.uuid = UUID.randomUUID().toString();
} }
public LdapConfigurationVO(final String hostname, final int port, final Long domainId) { public LdapConfigurationVO(final String hostname, final int port, final Long domainId) {
this.hostname = hostname; this.hostname = hostname;
this.port = port; this.port = port;
this.domainId = domainId; this.domainId = domainId;
this.uuid = UUID.randomUUID().toString();
} }
public String getHostname() { public String getHostname() {
@ -60,6 +68,10 @@ public class LdapConfigurationVO implements InternalIdentity {
return id; return id;
} }
public String getUuid() {
return uuid;
}
public int getPort() { public int getPort() {
return port; 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.LdapConfigurationDao;
import org.apache.cloudstack.ldap.dao.LdapTrustMapDao; import org.apache.cloudstack.ldap.dao.LdapTrustMapDao;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.cloud.domain.DomainVO; import com.cloud.domain.DomainVO;
@ -240,7 +241,7 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag
domainUuid = domain.getUuid(); domainUuid = domain.getUuid();
} }
} }
return new LdapConfigurationResponse(configuration.getHostname(), configuration.getPort(), domainUuid); return new LdapConfigurationResponse(configuration.getHostname(), configuration.getPort(), domainUuid, configuration.getUuid());
} }
@Override @Override
@ -257,6 +258,19 @@ public class LdapManagerImpl extends ComponentLifecycleBase implements LdapManag
@Override @Override
public LdapConfigurationResponse deleteConfiguration(final LdapDeleteConfigurationCmd cmd) throws InvalidParameterValueException { 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()); 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 int port = cmd.getPort();
final Long domainId = cmd.getDomainId(); final Long domainId = cmd.getDomainId();
final boolean listAll = cmd.listAll(); 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()); 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);
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(); listGlobalConfigurationsSearch.done();
listDomainConfigurationsSearch = createSearchBuilder(); listDomainConfigurationsSearch = createSearchBuilder();
listDomainConfigurationsSearch.and("id", listDomainConfigurationsSearch.entity().getId(), SearchCriteria.Op.EQ);
listDomainConfigurationsSearch.and("hostname", listDomainConfigurationsSearch.entity().getHostname(), Op.EQ); listDomainConfigurationsSearch.and("hostname", listDomainConfigurationsSearch.entity().getHostname(), Op.EQ);
listDomainConfigurationsSearch.and("port", listDomainConfigurationsSearch.entity().getPort(), Op.EQ); listDomainConfigurationsSearch.and("port", listDomainConfigurationsSearch.entity().getPort(), Op.EQ);
listDomainConfigurationsSearch.and("domain_id", listDomainConfigurationsSearch.entity().getDomainId(), Op.EQ); listDomainConfigurationsSearch.and("domain_id", listDomainConfigurationsSearch.entity().getDomainId(), Op.EQ);
@ -63,31 +64,35 @@ public class LdapConfigurationDaoImpl extends GenericDaoBase<LdapConfigurationVO
@Override @Override
public LdapConfigurationVO find(String hostname, int port, Long domainId) { 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); return findOneBy(sc);
} }
@Override @Override
public LdapConfigurationVO find(String hostname, int port, Long domainId, boolean listAll) { 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); return findOneBy(sc);
} }
@Override @Override
public Pair<List<LdapConfigurationVO>, Integer> searchConfigurations(final String hostname, final int port, final Long domainId) { 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); return searchAndCount(sc, null);
} }
@Override @Override
public Pair<List<LdapConfigurationVO>, Integer> searchConfigurations(final String hostname, final int port, final Long domainId, final boolean 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(hostname, port, domainId, listAll); SearchCriteria<LdapConfigurationVO> sc = getSearchCriteria(id, hostname, port, domainId, listAll);
return searchAndCount(sc, null); 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; 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 // If domainid is present, ignore listall
sc = listDomainConfigurationsSearch.create(); sc = listDomainConfigurationsSearch.create();
sc.setParameters("domain_id", domainId); sc.setParameters("domain_id", domainId);

View File

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

View File

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

View File

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