DnsNodeRepositoryImpl.java
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bremersee.dccon.repository;
import static org.bremersee.dccon.model.UnknownFilter.ALL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import org.bremersee.data.ldaptive.LdaptiveEntryMapper;
import org.bremersee.data.ldaptive.LdaptiveTemplate;
import org.bremersee.dccon.config.DomainControllerProperties;
import org.bremersee.dccon.model.DnsNode;
import org.bremersee.dccon.model.DnsRecord;
import org.bremersee.dccon.model.UnknownFilter;
import org.bremersee.dccon.repository.cli.CommandExecutor;
import org.bremersee.dccon.repository.ldap.DnsNodeLdapMapper;
import org.bremersee.exception.ServiceException;
import org.ldaptive.SearchFilter;
import org.ldaptive.SearchRequest;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* The dns node repository.
*
* @author Christian Bremer
*/
@Profile("ldap")
@Component("dnsNodeRepository")
@Slf4j
public class DnsNodeRepositoryImpl extends AbstractDnsNodeRepository {
private final Map<String, LdaptiveEntryMapper<DnsNode>> dnsNodeLdapMapperMap;
private DnsNodeLdapMapperProvider dnsNodeLdapMapperProvider;
/**
* Instantiates a new dns node repository.
*
* @param properties the properties
* @param ldapTemplateProvider the ldap template provider
* @param dhcpRepository the dhcp repository
* @param dnsZoneRepository the dns zone repository
*/
public DnsNodeRepositoryImpl(
final DomainControllerProperties properties,
final ObjectProvider<LdaptiveTemplate> ldapTemplateProvider,
final DhcpRepository dhcpRepository,
final DnsZoneRepository dnsZoneRepository) {
super(properties, ldapTemplateProvider.getIfAvailable(), dhcpRepository, dnsZoneRepository);
this.dnsNodeLdapMapperMap = new ConcurrentHashMap<>();
this.dnsNodeLdapMapperProvider = (zoneName, unknownFilter) -> new DnsNodeLdapMapper(
getProperties(), zoneName, unknownFilter);
}
/**
* Keep dhcp lease caches up to date.
*/
@Scheduled(fixedDelay = 30000L, initialDelay = 2000)
public void keepDhcpLeaseCachesUpToDate() {
log.trace("msg=[Keeping dhcp lease cache up to date.]");
getDhcpRepository().findActiveByIp();
getDhcpRepository().findActiveByHostName();
}
private LdaptiveEntryMapper<DnsNode> getDnsNodeLdapMapper(
final String zoneName,
final UnknownFilter unknownFilter) {
final UnknownFilter filter = unknownFilter(unknownFilter);
final String key = zoneName + ":" + filter.name();
return dnsNodeLdapMapperMap.computeIfAbsent(
key,
k -> dnsNodeLdapMapperProvider.getDnsNodeLdapMapper(zoneName, filter));
}
/**
* Sets dns node ldap mapper provider.
*
* @param dnsNodeLdapMapperProvider the dns node ldap mapper provider
*/
@SuppressWarnings("unused")
public void setDnsNodeLdapMapperProvider(
final DnsNodeLdapMapperProvider dnsNodeLdapMapperProvider) {
if (dnsNodeLdapMapperProvider != null) {
this.dnsNodeLdapMapperProvider = dnsNodeLdapMapperProvider;
}
}
@Override
public Stream<DnsNode> findAll(
final String zoneName,
final UnknownFilter unknownFilter,
final String query) {
final SearchRequest searchRequest = new SearchRequest(
getProperties().buildDnsNodeBaseDn(zoneName),
new SearchFilter(getProperties().getDnsNodeFindAllFilter()));
searchRequest.setSearchScope(getProperties().getDnsNodeFindAllSearchScope());
searchRequest.setBinaryAttributes("dnsRecord");
if (query == null || query.trim().length() == 0) {
return getLdapTemplate().findAll(searchRequest, getDnsNodeLdapMapper(zoneName, unknownFilter))
.filter(this::isNonExcludedDnsNode)
.map(dnsNode -> insertCorrelationValues(zoneName, dnsNode))
.map(dnsNode -> insertDhcpLeases(zoneName, dnsNode));
} else {
return getLdapTemplate().findAll(searchRequest, getDnsNodeLdapMapper(zoneName, unknownFilter))
.filter(this::isNonExcludedDnsNode)
.map(dnsNode -> insertCorrelationValues(zoneName, dnsNode))
.map(dnsNode -> insertDhcpLeases(zoneName, dnsNode))
.filter(dnsNode -> this.isQueryResult(dnsNode, query));
}
}
@Override
public boolean exists(
final String zoneName,
final String nodeName,
final UnknownFilter unknownFilter) {
return isNonExcludedDnsNode(nodeName)
&& getDnsZoneRepository().exists(zoneName)
&& getLdapTemplate().exists(DnsNode.builder().name(nodeName).build(),
getDnsNodeLdapMapper(zoneName, unknownFilter));
}
@Override
Optional<DnsNode> findOne(
final String zoneName,
final String nodeName,
final UnknownFilter unknownFilter,
final boolean withCorrelationValues,
final boolean withDhcpLeases) {
final SearchFilter searchFilter = new SearchFilter(getProperties().getDnsNodeFindOneFilter());
searchFilter.setParameter(0, nodeName);
final SearchRequest searchRequest = new SearchRequest(
getProperties().buildDnsNodeBaseDn(zoneName),
searchFilter);
searchRequest.setSearchScope(getProperties().getDnsNodeFindAllSearchScope());
searchRequest.setBinaryAttributes("dnsRecord");
return getLdapTemplate().findOne(searchRequest, getDnsNodeLdapMapper(zoneName, unknownFilter))
.filter(this::isNonExcludedDnsNode)
.map(dnsNode -> withCorrelationValues
? insertCorrelationValues(zoneName, dnsNode)
: dnsNode)
.map(dnsNode -> withDhcpLeases
? insertDhcpLeases(zoneName, dnsNode)
: dnsNode);
}
@Override
public Optional<DnsNode> save(
final String zoneName,
final DnsNode dnsNode) {
if (isExcludedDnsNode(dnsNode)) {
throw ServiceException.badRequest(
"Node name is not allowed.",
"org.bremersee:dc-con-app:8dd7165e-89af-4423-900a-5fc0a71fe7bf");
}
// Collect deleted records and save existing dns node
final Set<DnsRecord> deletedRecords = new LinkedHashSet<>();
DnsNode newDnsNode = findOne(zoneName, dnsNode.getName(), ALL, false, false)
.map(existingDnsNode -> {
for (final DnsRecord existingDnsRecord : existingDnsNode.getRecords()) {
if (!dnsNode.getRecords().contains(existingDnsRecord)) {
deletedRecords.add(existingDnsRecord);
}
}
if (deletedRecords.size() == existingDnsNode.getRecords().size()) {
getLdapTemplate().delete(
existingDnsNode,
getDnsNodeLdapMapper(zoneName, ALL));
return DnsNode.builder()
.name(dnsNode.getName())
.build();
}
return getLdapTemplate().save(dnsNode, getDnsNodeLdapMapper(zoneName, ALL));
})
.orElseGet(() -> DnsNode.builder()
.name(dnsNode.getName())
.build());
// Collect new records
final Set<DnsRecord> newRecords = new LinkedHashSet<>();
for (final DnsRecord record : dnsNode.getRecords()) {
if (!newDnsNode.getRecords().contains(record)) {
newRecords.add(record);
}
}
if (newDnsNode.getRecords().isEmpty() && newRecords.isEmpty()) {
// The dns node has no records, it will be deleted
if (StringUtils.hasText(newDnsNode.getDistinguishedName())) {
getLdapTemplate().delete(dnsNode, getDnsNodeLdapMapper(zoneName, ALL));
}
newDnsNode = null;
} else {
// Add new record via cli
add(zoneName, dnsNode.getName(), newRecords);
// Load dns node from ldap
newDnsNode = findOne(zoneName, dnsNode.getName(), ALL, false, false)
.orElseThrow(() -> ServiceException.internalServerError(
"Saving dns node failed.",
"org.bremersee:dc-con-app:7eabb994-f6db-49dc-870b-b4e2dd330a4c"));
}
// Do A record to PTR record synchronization
handlePtrRecords(zoneName, dnsNode.getName(), newRecords, deletedRecords);
return Optional.ofNullable(newDnsNode);
}
/**
* Add a dns node.
*
* @param zoneName the zone name
* @param nodeName the node name
* @param records the records
*/
void add(
final String zoneName,
final String nodeName,
final Collection<DnsRecord> records) {
if (zoneName == null || nodeName == null || records == null || records.isEmpty()) {
return;
}
kinit();
for (final DnsRecord record : records) {
final List<String> commands = new ArrayList<>();
sudo(commands);
commands.add(getProperties().getSambaToolBinary());
commands.add("dns");
commands.add("add");
commands.add(getProperties().getNameServerHost());
commands.add(zoneName);
commands.add(nodeName);
commands.add(record.getRecordType());
commands.add(record.getRecordValue());
auth(commands);
CommandExecutor.exec(commands, getProperties().getSambaToolExecDir());
}
}
@Override
public boolean delete(final String zoneName, final DnsNode node) {
if (isExcludedDnsNode(node)) {
throw ServiceException.badRequest(
"Node name is not allowed.",
"org.bremersee:dc-con-app:3e377240-eafd-45ea-9ee6-048ab3ca8cec");
}
getLdapTemplate().delete(node, getDnsNodeLdapMapper(zoneName, ALL));
handlePtrRecords(zoneName, node.getName(), Collections.emptySet(), node.getRecords());
return true;
}
}