package io.fathom.cloud.dns.backend.federated;
import io.fathom.cloud.CloudException;
import io.fathom.cloud.dns.backend.DnsBackendBase;
import io.fathom.cloud.dns.model.DnsZone;
import io.fathom.cloud.openstack.client.OpenstackClient;
import io.fathom.cloud.openstack.client.RestClientException;
import io.fathom.cloud.openstack.client.dns.OpenstackDnsClient;
import io.fathom.cloud.openstack.client.dns.model.Recordset;
import io.fathom.cloud.openstack.client.dns.model.Zone;
import io.fathom.cloud.openstack.client.identity.CertificateAuthTokenProvider;
import io.fathom.cloud.openstack.client.identity.ChallengeResponses;
import io.fathom.cloud.openstack.client.identity.OpenstackIdentityClient;
import io.fathom.cloud.protobuf.DnsModel.BackendData;
import io.fathom.cloud.protobuf.DnsModel.DnsBackendProviderType;
import io.fathom.cloud.protobuf.DnsModel.DnsSuffixData;
import io.fathom.cloud.server.model.Project;
import io.fathom.cloud.ssh.SshContext;
import io.fathom.cloud.ssh.jsch.SshContextImpl;
import io.fathom.cloud.state.DuplicateValueException;
import io.fathom.cloud.tasks.TaskScheduler;
import io.fathom.http.HttpClient;
import io.fathom.http.jre.JreHttpClient;
import java.io.IOException;
import java.net.URI;
import java.security.KeyPair;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.security.auth.x500.X500Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fathomdb.crypto.CertificateAndKey;
@Singleton
public class FederatedDnsBackend extends DnsBackendBase {
private static final Logger log = LoggerFactory.getLogger(FederatedDnsBackend.class);
@Inject
TaskScheduler taskScheduler;
@Inject
SshContext sshContext;
private BackendData backendData;
OpenstackClient getOpenstackClient() {
KeyPair keypair = getKeyPair();
URI uri = URI.create(backendData.getUrl());
HttpClient httpClient = JreHttpClient.create();
OpenstackIdentityClient identityClient = new OpenstackIdentityClient(httpClient, uri, null);
X500Principal subject = new X500Principal("CN=" + "unknown");
CertificateAndKey certificateAndKey = ChallengeResponses.createSelfSigned(subject, keypair);
String project = backendData.getBackendCookie();
if (project == null) {
throw new IllegalStateException();
// log.warn("No backend project configured: {}",
// backendData);
// project = "__federation__";
}
CertificateAuthTokenProvider tokenProvider = new CertificateAuthTokenProvider(identityClient, project,
certificateAndKey);
OpenstackClient openstackClient = OpenstackClient.build(tokenProvider);
return openstackClient;
}
KeyPair getKeyPair() {
KeyPair keypair = ((SshContextImpl) sshContext).getKeypair();
return keypair;
}
public void init(BackendData backendData) {
this.backendData = backendData;
}
@Override
public void updateDomain(Project project, DnsZone domain) {
UpdateFederated job = new UpdateFederated(project, domain);
taskScheduler.execute(job);
}
@Override
public String createZone(Project project, String zone, String topZone, DnsSuffixData suffixData)
throws CloudException {
try {
OpenstackClient openstackClient = getOpenstackClient();
OpenstackDnsClient dns = openstackClient.getDns();
Zone request = new Zone();
request.name = zone;
Zone response = dns.createZone(request);
return response.id;
} catch (RestClientException e) {
if (e.is(409)) {
throw new DuplicateValueException();
}
throw new CloudException("Error creating zone", e);
}
}
@Override
public DnsBackendProviderType getType() {
return DnsBackendProviderType.OPENSTACK;
}
public class UpdateFederated extends UpdateDnsDomainBase {
protected final OpenstackClient openstackClient;
public UpdateFederated(Project project, DnsZone domain) {
super(project, domain);
this.openstackClient = getOpenstackClient();
}
@Override
public Void call() throws CloudException, IOException {
Zone zone = getZone();
List<Recordset> requested = readFromDatabase(true);
List<Recordset> current;
try {
current = readFromOpenstack(zone);
} catch (RestClientException e) {
throw new CloudException("Error reading zone", e);
}
Changes changes = computeChanges(current, requested);
try {
OpenstackDnsClient client = openstackClient.getDns();
for (Recordset r : changes.remove) {
client.deleteRecordset(zone.id, r.id);
}
for (Recordset r : changes.create) {
client.createRecordset(zone.id, r);
}
} catch (RestClientException e) {
throw new CloudException("Error applying zone changes", e);
}
return null;
}
private List<Recordset> readFromOpenstack(Zone zone) throws RestClientException {
OpenstackDnsClient client = openstackClient.getDns();
List<Recordset> recordsets = client.listRecordsets(zone.id, true);
return recordsets;
}
private Zone getZone() throws CloudException {
String zoneName = this.zone.getName();
try {
OpenstackDnsClient client = openstackClient.getDns();
List<Zone> zones = client.listZones();
Zone zone = null;
for (Zone z : zones) {
if (zoneName.equals(z.name)) {
zone = z;
break;
}
}
if (zone == null) {
zone = new Zone();
zone.name = zoneName;
zone = client.createZone(zone);
}
return zone;
} catch (RestClientException e) {
throw new CloudException("Error mapping zone: " + zoneName, e);
}
}
}
}