| /* |
| * 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; |
| } |
| |
| } |