blob: 84c1b5b44e57909736d0878f5644f091c3426c3a [file] [log] [blame]
/*
* 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.
*/
package com.globo.globodns.cloudstack.resource;
import java.util.List;
import java.util.Map;
import javax.naming.ConfigurationException;
import org.apache.log4j.Logger;
import com.cloud.agent.IAgentControl;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.MaintainAnswer;
import com.cloud.agent.api.MaintainCommand;
import com.cloud.agent.api.PingCommand;
import com.cloud.agent.api.ReadyAnswer;
import com.cloud.agent.api.ReadyCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.host.Host;
import com.cloud.host.Host.Type;
import com.cloud.resource.ServerResource;
import com.cloud.utils.component.ManagerBase;
import com.globo.globodns.client.GloboDns;
import com.globo.globodns.client.GloboDnsException;
import com.globo.globodns.client.model.Authentication;
import com.globo.globodns.client.model.Domain;
import com.globo.globodns.client.model.Export;
import com.globo.globodns.client.model.Record;
import com.globo.globodns.cloudstack.commands.CreateOrUpdateDomainCommand;
import com.globo.globodns.cloudstack.commands.CreateOrUpdateRecordAndReverseCommand;
import com.globo.globodns.cloudstack.commands.RemoveDomainCommand;
import com.globo.globodns.cloudstack.commands.RemoveRecordCommand;
import com.globo.globodns.cloudstack.commands.SignInCommand;
public class GloboDnsResource extends ManagerBase implements ServerResource {
private String _zoneId;
private String _guid;
private String _name;
private String _username;
private String _url;
private String _password;
protected GloboDns _globoDns;
private static final String IPV4_RECORD_TYPE = "A";
private static final String REVERSE_RECORD_TYPE = "PTR";
private static final String REVERSE_DOMAIN_SUFFIX = "in-addr.arpa";
private static final String DEFAULT_AUTHORITY_TYPE = "M";
private static final Logger s_logger = Logger.getLogger(GloboDnsResource.class);
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
_zoneId = (String)params.get("zoneId");
if (_zoneId == null) {
throw new ConfigurationException("Unable to find zone");
}
_guid = (String)params.get("guid");
if (_guid == null) {
throw new ConfigurationException("Unable to find guid");
}
_name = (String)params.get("name");
if (_name == null) {
throw new ConfigurationException("Unable to find name");
}
_url = (String)params.get("url");
if (_url == null) {
throw new ConfigurationException("Unable to find url");
}
_username = (String)params.get("username");
if (_username == null) {
throw new ConfigurationException("Unable to find username");
}
_password = (String)params.get("password");
if (_password == null) {
throw new ConfigurationException("Unable to find password");
}
_globoDns = GloboDns.buildHttpApi(_url, _username, _password);
return true;
}
@Override
public boolean start() {
return true;
}
@Override
public boolean stop() {
return true;
}
@Override
public Type getType() {
return Host.Type.L2Networking;
}
@Override
public StartupCommand[] initialize() {
s_logger.trace("initialize called");
StartupCommand cmd = new StartupCommand(getType());
cmd.setName(_name);
cmd.setGuid(_guid);
cmd.setDataCenter(_zoneId);
cmd.setPod("");
cmd.setPrivateIpAddress("");
cmd.setStorageIpAddress("");
cmd.setVersion(GloboDnsResource.class.getPackage().getImplementationVersion());
return new StartupCommand[] {cmd};
}
@Override
public PingCommand getCurrentStatus(long id) {
return new PingCommand(getType(), id);
}
@Override
public void disconnected() {
return;
}
@Override
public IAgentControl getAgentControl() {
return null;
}
@Override
public void setAgentControl(IAgentControl agentControl) {
return;
}
@Override
public Answer executeRequest(Command cmd) {
if (cmd instanceof ReadyCommand) {
return new ReadyAnswer((ReadyCommand)cmd);
} else if (cmd instanceof MaintainCommand) {
return new MaintainAnswer((MaintainCommand)cmd);
} else if (cmd instanceof SignInCommand) {
return execute((SignInCommand)cmd);
} else if (cmd instanceof RemoveDomainCommand) {
return execute((RemoveDomainCommand)cmd);
} else if (cmd instanceof RemoveRecordCommand) {
return execute((RemoveRecordCommand)cmd);
} else if (cmd instanceof CreateOrUpdateDomainCommand) {
return execute((CreateOrUpdateDomainCommand)cmd);
} else if (cmd instanceof CreateOrUpdateRecordAndReverseCommand) {
return execute((CreateOrUpdateRecordAndReverseCommand)cmd);
}
return Answer.createUnsupportedCommandAnswer(cmd);
}
public Answer execute(SignInCommand cmd) {
try {
Authentication auth = _globoDns.getAuthAPI().signIn(cmd.getEmail(), cmd.getPassword());
if (auth != null) {
return new Answer(cmd, true, "Signed in successfully");
} else {
return new Answer(cmd, false, "Unable to sign in on GloboDNS");
}
} catch (GloboDnsException e) {
return new Answer(cmd, false, e.getMessage());
}
}
public Answer execute(RemoveDomainCommand cmd) {
try {
Domain domain = searchDomain(cmd.getNetworkDomain(), false);
if (domain != null) {
if (!cmd.isOverride()) {
for (Record record : _globoDns.getRecordAPI().listAll(domain.getId())) {
if (record.getTypeNSRecordAttributes().getId() == null) {
s_logger.warn("There are records in domain " + cmd.getNetworkDomain() + " and override is not enable. I will not delete this domain.");
return new Answer(cmd, true, "Domain keeped");
}
}
}
_globoDns.getDomainAPI().removeDomain(domain.getId());
scheduleExportChangesToBind();
} else {
s_logger.warn("Domain " + cmd.getNetworkDomain() + " already been deleted.");
}
return new Answer(cmd, true, "Domain removed");
} catch (GloboDnsException e) {
return new Answer(cmd, false, e.getMessage());
}
}
public Answer execute(RemoveRecordCommand cmd) {
boolean needsExport = false;
try {
if (removeRecord(cmd.getRecordName(), cmd.getRecordIp(), cmd.getNetworkDomain(), false, cmd.isOverride())) {
needsExport = true;
}
// remove reverse
String reverseGloboDnsName = generateReverseDomainNameFromNetworkIp(cmd.getRecordIp());
String reverseRecordName = generateReverseRecordNameFromNetworkIp(cmd.getRecordIp());
String reverseRecordContent = cmd.getRecordName() + '.' + cmd.getNetworkDomain();
if (removeRecord(reverseRecordName, reverseRecordContent, reverseGloboDnsName, true, cmd.isOverride())) {
needsExport = true;
}
return new Answer(cmd, true, "Record removed");
} catch (GloboDnsException e) {
return new Answer(cmd, false, e.getMessage());
} finally {
if (needsExport) {
scheduleExportChangesToBind();
}
}
}
public Answer execute(CreateOrUpdateRecordAndReverseCommand cmd) {
boolean needsExport = false;
try {
Domain domain = searchDomain(cmd.getNetworkDomain(), false);
if (domain == null) {
domain = _globoDns.getDomainAPI().createDomain(cmd.getNetworkDomain(), cmd.getReverseTemplateId(), DEFAULT_AUTHORITY_TYPE);
s_logger.warn("Domain " + cmd.getNetworkDomain() + " doesn't exist, maybe someone removed it. It was automatically created with template "
+ cmd.getReverseTemplateId());
}
boolean created = createOrUpdateRecord(domain.getId(), cmd.getRecordName(), cmd.getRecordIp(), IPV4_RECORD_TYPE, cmd.isOverride());
if (!created) {
String msg = "Unable to create record " + cmd.getRecordName() + " at " + cmd.getNetworkDomain();
if (!cmd.isOverride()) {
msg += ". Override record option is false, maybe record already exist.";
}
return new Answer(cmd, false, msg);
} else {
needsExport = true;
}
String reverseRecordContent = cmd.getRecordName() + '.' + cmd.getNetworkDomain();
if (createOrUpdateReverse(cmd.getRecordIp(), reverseRecordContent, cmd.getReverseTemplateId(), cmd.isOverride())) {
needsExport = true;
} else {
if (!cmd.isOverride()) {
String msg = "Unable to create reverse record " + cmd.getRecordName() + " for ip " + cmd.getRecordIp();
msg += ". Override record option is false, maybe record already exist.";
return new Answer(cmd, false, msg);
}
}
return new Answer(cmd);
} catch (GloboDnsException e) {
return new Answer(cmd, false, e.getMessage());
} finally {
if (needsExport) {
scheduleExportChangesToBind();
}
}
}
protected boolean createOrUpdateReverse(String networkIp, String reverseRecordContent, Long templateId, boolean override) {
String reverseDomainName = generateReverseDomainNameFromNetworkIp(networkIp);
Domain reverseDomain = searchDomain(reverseDomainName, true);
if (reverseDomain == null) {
reverseDomain = _globoDns.getDomainAPI().createReverseDomain(reverseDomainName, templateId, DEFAULT_AUTHORITY_TYPE);
s_logger.info("Created reverse domain " + reverseDomainName + " with template " + templateId);
}
// create reverse
String reverseRecordName = generateReverseRecordNameFromNetworkIp(networkIp);
return createOrUpdateRecord(reverseDomain.getId(), reverseRecordName, reverseRecordContent, REVERSE_RECORD_TYPE, override);
}
public Answer execute(CreateOrUpdateDomainCommand cmd) {
boolean needsExport = false;
try {
Domain domain = searchDomain(cmd.getDomainName(), false);
if (domain == null) {
// create
domain = _globoDns.getDomainAPI().createDomain(cmd.getDomainName(), cmd.getTemplateId(), DEFAULT_AUTHORITY_TYPE);
s_logger.info("Created domain " + cmd.getDomainName() + " with template " + cmd.getTemplateId());
if (domain == null) {
return new Answer(cmd, false, "Unable to create domain " + cmd.getDomainName());
} else {
needsExport = true;
}
} else {
s_logger.warn("Domain " + cmd.getDomainName() + " already exist.");
}
return new Answer(cmd);
} catch (GloboDnsException e) {
return new Answer(cmd, false, e.getMessage());
} finally {
if (needsExport) {
scheduleExportChangesToBind();
}
}
}
/**
* Try to remove a record from bindZoneName. If record was removed returns true.
* @param recordName
* @param bindZoneName
* @return true if record exists and was removed.
*/
protected boolean removeRecord(String recordName, String recordValue, String bindZoneName, boolean reverse, boolean override) {
Domain domain = searchDomain(bindZoneName, reverse);
if (domain == null) {
s_logger.warn("Domain " + bindZoneName + " doesn't exists in GloboDNS. Record " + recordName + " has already been removed.");
return false;
}
Record record = searchRecord(recordName, domain.getId());
if (record == null) {
s_logger.warn("Record " + recordName + " in domain " + bindZoneName + " has already been removed.");
return false;
} else {
if (!override && !record.getContent().equals(recordValue)) {
s_logger.warn("Record " + recordName + " in domain " + bindZoneName + " have different value from " + recordValue
+ " and override is not enable. I will not delete it.");
return false;
}
_globoDns.getRecordAPI().removeRecord(record.getId());
}
return true;
}
/**
* Create a new record in Zone, or update it if record has been exists.
* @param domainId
* @param name
* @param ip
* @param type
* @return if record was created or updated.
*/
private boolean createOrUpdateRecord(Long domainId, String name, String ip, String type, boolean override) {
Record record = this.searchRecord(name, domainId);
if (record == null) {
// Create new record
record = _globoDns.getRecordAPI().createRecord(domainId, name, ip, type);
s_logger.info("Created record " + record.getName() + " in domain " + domainId);
} else {
if (!ip.equals(record.getContent())) {
if (Boolean.TRUE.equals(override)) {
// ip is incorrect. Fix.
_globoDns.getRecordAPI().updateRecord(record.getId(), domainId, name, ip);
} else {
return false;
}
}
}
return true;
}
/**
* GloboDns export all changes to Bind server.
*/
public void scheduleExportChangesToBind() {
try {
Export export = _globoDns.getExportAPI().scheduleExport();
if (export != null) {
s_logger.info("GloboDns Export: " + export.getResult());
}
} catch (GloboDnsException e) {
s_logger.warn("Error on scheduling export. Although everything was persist, someone need to manually force export in GloboDns", e);
}
}
/**
* Try to find bindZoneName in GloboDns.
* @param name
* @return Domain object or null if domain not exists.
*/
private Domain searchDomain(String name, boolean reverse) {
if (name == null) {
return null;
}
List<Domain> candidates;
if (reverse) {
candidates = _globoDns.getDomainAPI().listReverseByQuery(name);
} else {
candidates = _globoDns.getDomainAPI().listByQuery(name);
}
for (Domain candidate : candidates) {
if (name.equals(candidate.getName())) {
return candidate;
}
}
return null;
}
/**
* Find recordName in domain.
* @param recordName
* @param domainId Id of BindZoneName. Maybe you need use searchDomain before to use BindZoneName.
* @return Record or null if not exists.
*/
private Record searchRecord(String recordName, Long domainId) {
if (recordName == null || domainId == null) {
return null;
}
List<Record> candidates = _globoDns.getRecordAPI().listByQuery(domainId, recordName);
// GloboDns search name in name and content. We need to iterate to check if recordName exists only in name
for (Record candidate : candidates) {
if (recordName.equalsIgnoreCase(candidate.getName())) {
s_logger.debug("Record " + recordName + " in domain id " + domainId + " found in GloboDNS");
return candidate;
}
}
s_logger.debug("Record " + recordName + " in domain id " + domainId + " not found in GloboDNS");
return null;
}
/**
* Generate reverseBindZoneName of network. We ALWAYS use /24.
* @param networkIp
* @return Bind Zone Name reverse of network specified by networkIp
*/
private String generateReverseDomainNameFromNetworkIp(String networkIp) {
String[] octets = networkIp.split("\\.");
String reverseDomainName = octets[2] + '.' + octets[1] + '.' + octets[0] + '.' + REVERSE_DOMAIN_SUFFIX;
return reverseDomainName;
}
private String generateReverseRecordNameFromNetworkIp(String networkIp) {
String[] octets = networkIp.split("\\.");
String reverseRecordName = octets[3];
return reverseRecordName;
}
}