CLOUDSTACK-6998: GloboDNS, Integration with external DNS Provider

This is a feature to handle DNS entries by means of an external DNS Provider,
such as Bind. These entries include DNS domains and reverse domains, VM records
and reverse records.

For a complete description, please refer to the design document available at
https://cwiki.apache.org/confluence/display/CLOUDSTACK/Bind+and+PowerDNS+integration+by+Globo+DNSAPI

For the discussion about this feature on the dev mailing list, please refer to
http://markmail.org/thread/fvwf36hpxotiibka

Summary:
- new Network Service Provider called GloboDNS
- new Network Element to manage network domains and VM records (entries) on an external API
- new Network Resource to communicate with GloboDNS (open source)
- new API command to add DNS server
- new global option to determine if this provider should override VM entries on external DNS server
- changes in UI to include GloboDNS in Providers list

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Daniel Vega 2014-08-20 15:49:40 -03:00 committed by Rohit Yadav
parent 7ada4ad50b
commit 233445ed68
27 changed files with 2532 additions and 2 deletions

View File

@ -136,6 +136,8 @@ public interface Network extends ControlledEntity, StateObject<Network.State>, I
public static final Provider NuageVsp = new Provider("NuageVsp", false);
public static final Provider NuageVspVpc = new Provider("NuageVspVpc", false);
public static final Provider BrocadeVcs = new Provider("BrocadeVcs", false);
// add GloboDns provider
public static final Provider GloboDns = new Provider("GloboDns", true);
private final String name;
private final boolean isExternal;

View File

@ -47,6 +47,7 @@ public interface ExternalNetworkDeviceManager extends Manager {
public static final NetworkDevice CiscoVnmc = new NetworkDevice("CiscoVnmc", Network.Provider.CiscoVnmc.getName());
public static final NetworkDevice OpenDaylightController = new NetworkDevice("OpenDaylightController", Network.Provider.Opendaylight.getName());
public static final NetworkDevice BrocadeVcs = new NetworkDevice("BrocadeVcs", Network.Provider.BrocadeVcs.getName());
public static final NetworkDevice GloboDns = new NetworkDevice("GloboDns", Network.Provider.GloboDns.getName());
public NetworkDevice(String deviceName, String ntwkServiceprovider) {
_name = deviceName;

View File

@ -346,6 +346,11 @@
<artifactId>cloud-plugin-api-solidfire-intg-test</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-network-globodns</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -763,3 +763,6 @@ createServiceInstance=1
addOpenDaylightController=1
deleteOpenDaylightController=1
listOpenDaylightControllers=1
### GloboDNS commands
addGloboDnsHost=1

View File

@ -0,0 +1,37 @@
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-plugin-network-globodns</artifactId>
<name>Apache CloudStack Plugin - GloboDNS</name>
<parent>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloudstack-plugins</artifactId>
<version>4.5.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>com.globo.globodns</groupId>
<artifactId>globodns-client</artifactId>
<version>0.0.15</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,18 @@
# 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.
name=globodns
parent=network

View File

@ -0,0 +1,22 @@
<!-- 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. -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.globo.globodns.cloudstack" />
</beans>

View File

@ -0,0 +1,124 @@
// 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.api;
import javax.inject.Inject;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.PhysicalNetworkResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.context.CallContext;
import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.host.Host;
import com.cloud.utils.exception.CloudRuntimeException;
import com.globo.globodns.cloudstack.element.GloboDnsElementService;
@APICommand(name = "addGloboDnsHost", responseObject = SuccessResponse.class, description = "Adds the GloboDNS external host", since="4.5.0")
public class AddGloboDnsHostCmd extends BaseAsyncCmd {
private static final String s_name = "addglobodnshostresponse";
@Inject
GloboDnsElementService _globoDnsElementService;
// ///////////////////////////////////////////////////
// ////////////// API parameters /////////////////////
// ///////////////////////////////////////////////////
@Parameter(name = ApiConstants.PHYSICAL_NETWORK_ID, type = CommandType.UUID, entityType = PhysicalNetworkResponse.class, required = true, description = "the Physical Network ID")
private Long physicalNetworkId;
@Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, required = true, description = "Username for GloboDNS")
private String username;
@Parameter(name = ApiConstants.PASSWORD, type = CommandType.STRING, required = true, description = "Password for GloboDNS")
private String password;
@Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "GloboDNS url")
private String url;
// ///////////////////////////////////////////////////
// ///////////////// Accessors ///////////////////////
// ///////////////////////////////////////////////////
public Long getPhysicalNetworkId() {
return physicalNetworkId;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getUrl() {
return url;
}
// ///////////////////////////////////////////////////
// ///////////// API Implementation///////////////////
// ///////////////////////////////////////////////////
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException {
try {
Host host = _globoDnsElementService.addGloboDnsHost(physicalNetworkId, username, password, url);
SuccessResponse response = new SuccessResponse(getCommandName());
response.setSuccess((host == null ? false : true));
this.setResponseObject(response);
} catch (InvalidParameterValueException invalidParamExcp) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, invalidParamExcp.getMessage());
} catch (CloudRuntimeException runtimeExcp) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, runtimeExcp.getMessage());
}
}
@Override
public String getCommandName() {
return s_name;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccountId();
}
@Override
public String getEventType() {
//EventTypes.EVENT_NETWORK_CREATE
return EventTypes.EVENT_NETWORK_CREATE;
}
@Override
public String getEventDescription() {
return "Add GloboDNS provider";
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.commands;
import com.cloud.agent.api.Command;
public class CreateOrUpdateDomainCommand extends Command {
private String domainName;
private Long templateId;
public CreateOrUpdateDomainCommand(String domainName, Long templateId) {
this.domainName = domainName;
this.templateId = templateId;
}
@Override
public boolean executeInSequence() {
return false;
}
public String getDomainName() {
return domainName;
}
public Long getTemplateId() {
return templateId;
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.commands;
import com.cloud.agent.api.Command;
public class CreateOrUpdateRecordAndReverseCommand extends Command {
private String recordName;
private String recordIp;
private String networkDomain;
private Long reverseTemplateId;
private boolean override;
public CreateOrUpdateRecordAndReverseCommand(String recordName, String recordIp, String networkDomain, Long reverseTemplateId, boolean override) {
this.recordName = recordName;
this.recordIp = recordIp;
this.networkDomain = networkDomain;
this.reverseTemplateId = reverseTemplateId;
this.override = override;
}
@Override
public boolean executeInSequence() {
return false;
}
public String getRecordName() {
return this.recordName;
}
public String getRecordIp() {
return this.recordIp;
}
public String getNetworkDomain() {
return this.networkDomain;
}
public Long getReverseTemplateId() {
return reverseTemplateId;
}
public boolean isOverride() {
return override;
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.commands;
import com.cloud.agent.api.Command;
public class RemoveDomainCommand extends Command {
private String networkDomain;
private boolean override;
public RemoveDomainCommand(String networkDomain, boolean override) {
this.networkDomain = networkDomain;
this.override = override;
}
@Override
public boolean executeInSequence() {
return false;
}
public String getNetworkDomain() {
return this.networkDomain;
}
public boolean isOverride() {
return override;
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.commands;
import com.cloud.agent.api.Command;
public class RemoveRecordCommand extends Command {
private String recordName;
private String recordIp;
private String networkDomain;
private boolean override;
public RemoveRecordCommand(String recordName, String recordIp, String networkDomain, boolean override) {
this.recordName = recordName;
this.recordIp = recordIp;
this.networkDomain = networkDomain;
this.override = override;
}
@Override
public boolean executeInSequence() {
return false;
}
public String getRecordName() {
return recordName;
}
public String getRecordIp() {
return recordIp;
}
public String getNetworkDomain() {
return networkDomain;
}
public boolean isOverride() {
return override;
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.commands;
import com.cloud.agent.api.Command;
public class SignInCommand extends Command {
private String email;
private String password;
public SignInCommand(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public boolean executeInSequence() {
return false;
}
public String getEmail() {
return this.email;
}
public String getPassword() {
return this.password;
}
}

View File

@ -0,0 +1,388 @@
// 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
// 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.element;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ejb.Local;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.StartupCommand;
import com.cloud.dc.DataCenter;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.deploy.DeployDestination;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.host.Host;
import com.cloud.host.Host.Type;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.network.Network;
import com.cloud.network.Network.Capability;
import com.cloud.network.Network.Provider;
import com.cloud.network.Network.Service;
import com.cloud.network.PhysicalNetwork;
import com.cloud.network.PhysicalNetworkServiceProvider;
import com.cloud.network.dao.PhysicalNetworkDao;
import com.cloud.network.element.NetworkElement;
import com.cloud.offering.NetworkOffering;
import com.cloud.resource.ResourceManager;
import com.cloud.resource.ResourceStateAdapter;
import com.cloud.resource.ServerResource;
import com.cloud.resource.UnableDeleteHostException;
import com.cloud.utils.component.AdapterBase;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallbackWithException;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.NicProfile;
import com.cloud.vm.ReservationContext;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineProfile;
import com.globo.globodns.cloudstack.api.AddGloboDnsHostCmd;
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;
import com.globo.globodns.cloudstack.resource.GloboDnsResource;
@Component
@Local(NetworkElement.class)
public class GloboDnsElement extends AdapterBase implements ResourceStateAdapter, NetworkElement, GloboDnsElementService, Configurable {
private static final Logger s_logger = Logger.getLogger(GloboDnsElement.class);
private static final Map<Service, Map<Capability, String>> capabilities = setCapabilities();
private static final ConfigKey<Long> GloboDNSTemplateId = new ConfigKey<Long>("Advanced", Long.class, "globodns.domain.templateid", "1",
"Template id to be used when creating domains in GloboDNS", true, ConfigKey.Scope.Global);
private static final ConfigKey<Boolean> GloboDNSOverride = new ConfigKey<Boolean>("Advanced", Boolean.class, "globodns.override.entries", "true",
"Allow GloboDns to override entries that already exist", true, ConfigKey.Scope.Global);
// DAOs
@Inject
DataCenterDao _dcDao;
@Inject
HostDao _hostDao;
@Inject
PhysicalNetworkDao _physicalNetworkDao;
// Managers
@Inject
AgentManager _agentMgr;
@Inject
ResourceManager _resourceMgr;
protected boolean isTypeSupported(VirtualMachine.Type type) {
return type == VirtualMachine.Type.User || type == VirtualMachine.Type.ConsoleProxy || type == VirtualMachine.Type.DomainRouter;
}
@Override
@DB
public boolean implement(final Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException,
ResourceUnavailableException, InsufficientCapacityException {
Long zoneId = network.getDataCenterId();
DataCenter zone = _dcDao.findById(zoneId);
if (zone == null) {
throw new CloudRuntimeException("Could not find zone associated to this network");
}
CreateOrUpdateDomainCommand cmd = new CreateOrUpdateDomainCommand(network.getNetworkDomain(), GloboDNSTemplateId.value());
callCommand(cmd, zoneId);
return true;
}
protected String hostNameOfVirtualMachine(VirtualMachineProfile vm) {
return vm.getHostName().toLowerCase();
}
@Override
@DB
public boolean prepare(final Network network, final NicProfile nic, final VirtualMachineProfile vm, DeployDestination dest, ReservationContext context)
throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
if (!isTypeSupported(vm.getType())) {
s_logger.info("GloboDNS only manages records for VMs of type User, ConsoleProxy and DomainRouter. VM " + vm + " is " + vm.getType());
return false;
}
Long zoneId = network.getDataCenterId();
final DataCenter zone = _dcDao.findById(zoneId);
if (zone == null) {
throw new CloudRuntimeException("Could not find zone associated to this network");
}
/* Create new A record in GloboDNS */
// We allow only lower case names in DNS, so force lower case names for VMs
String vmName = vm.getHostName();
String vmHostname = hostNameOfVirtualMachine(vm);
if (!vmName.equals(vmHostname) && vm.getType() == VirtualMachine.Type.User) {
throw new InvalidParameterValueException("VM name should contain only lower case letters and digits: " + vmName + " - " + vm);
}
CreateOrUpdateRecordAndReverseCommand cmd = new CreateOrUpdateRecordAndReverseCommand(vmHostname, nic.getIp4Address(), network.getNetworkDomain(),
GloboDNSTemplateId.value(), GloboDNSOverride.value());
callCommand(cmd, zoneId);
return true;
}
@Override
@DB
public boolean release(final Network network, NicProfile nic, final VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException,
ResourceUnavailableException {
if (!isTypeSupported(vm.getType())) {
s_logger.info("GloboDNS only manages records for VMs of type User, ConsoleProxy and DomainRouter. VM " + vm + " is " + vm.getType());
return false;
}
Long zoneId = network.getDataCenterId();
final DataCenter zone = _dcDao.findById(zoneId);
if (zone == null) {
throw new CloudRuntimeException("Could not find zone associated to this network");
}
RemoveRecordCommand cmd = new RemoveRecordCommand(hostNameOfVirtualMachine(vm), nic.getIp4Address(), network.getNetworkDomain(), GloboDNSOverride.value());
callCommand(cmd, zoneId);
return true;
}
@Override
public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
return true;
}
@Override
@DB
public boolean destroy(final Network network, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException {
Long zoneId = network.getDataCenterId();
final DataCenter zone = _dcDao.findById(zoneId);
if (zone == null) {
throw new CloudRuntimeException("Could not find zone associated to this network");
}
RemoveDomainCommand cmd = new RemoveDomainCommand(network.getNetworkDomain(), GloboDNSOverride.value());
callCommand(cmd, zoneId);
return true;
}
///////// Provider control methods ////////////
private Answer callCommand(Command cmd, Long zoneId) {
HostVO globoDnsHost = getGloboDnsHost(zoneId);
if (globoDnsHost == null) {
throw new CloudRuntimeException("Could not find the GloboDNS resource");
}
Answer answer = _agentMgr.easySend(globoDnsHost.getId(), cmd);
if (answer == null || !answer.getResult()) {
String msg = "Error executing command " + cmd;
msg = answer == null ? msg : answer.getDetails();
throw new CloudRuntimeException(msg);
}
return answer;
}
@Override
public Map<Service, Map<Capability, String>> getCapabilities() {
return capabilities;
}
private static Map<Service, Map<Capability, String>> setCapabilities() {
Map<Service, Map<Capability, String>> caps = new HashMap<Service, Map<Capability, String>>();
Map<Capability, String> dnsCapabilities = new HashMap<Capability, String>();
// FIXME
dnsCapabilities.put(Capability.AllowDnsSuffixModification, "true");
caps.put(Service.Dns, dnsCapabilities);
return caps;
}
@Override
public Provider getProvider() {
return Provider.GloboDns;
}
@Override
public boolean isReady(PhysicalNetworkServiceProvider provider) {
return true;
}
@Override
public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException {
PhysicalNetwork pNtwk = _physicalNetworkDao.findById(provider.getPhysicalNetworkId());
Host host = getGloboDnsHost(pNtwk.getDataCenterId());
if (host != null) {
_resourceMgr.deleteHost(host.getId(), true, false);
}
return true;
}
@Override
public boolean canEnableIndividualServices() {
return true;
}
@Override
public boolean verifyServicesCombination(Set<Service> services) {
return true;
}
////// Configurable methods /////////////
@Override
public String getConfigComponentName() {
return GloboDnsElement.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {GloboDNSTemplateId, GloboDNSOverride};
}
////////// Resource/Host methods ////////////
@Override
public List<Class<?>> getCommands() {
List<Class<?>> cmdList = new ArrayList<Class<?>>();
cmdList.add(AddGloboDnsHostCmd.class);
return cmdList;
}
@Override
public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) {
return null;
}
@Override
public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map<String, String> details, List<String> hostTags) {
if (!(startup[0] instanceof StartupCommand && resource instanceof GloboDnsResource)) {
return null;
}
host.setType(Host.Type.L2Networking);
return host;
}
@Override
public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException {
if (!(host.getType() == Host.Type.L2Networking)) {
return null;
}
return new DeleteHostAnswer(true);
}
@Override
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
super.configure(name, params);
_resourceMgr.registerResourceStateAdapter(name, this);
return true;
}
private HostVO getGloboDnsHost(Long zoneId) {
return _hostDao.findByTypeNameAndZoneId(zoneId, Provider.GloboDns.getName(), Type.L2Networking);
}
@Override
@DB
public Host addGloboDnsHost(Long physicalNetworkId, final String username, final String password, String url) {
if (username == null || username.trim().isEmpty()) {
throw new InvalidParameterValueException("Invalid username: " + username);
}
if (password == null || password.trim().isEmpty()) {
throw new InvalidParameterValueException("Invalid password: " + password);
}
if (url == null || url.trim().isEmpty()) {
throw new InvalidParameterValueException("Invalid url: " + url);
}
// validate physical network and zone
// Check if physical network exists
PhysicalNetwork pNtwk = null;
if (physicalNetworkId != null) {
pNtwk = _physicalNetworkDao.findById(physicalNetworkId);
if (pNtwk == null) {
throw new InvalidParameterValueException("Unable to find a physical network having the specified physical network id");
}
} else {
throw new InvalidParameterValueException("Invalid physicalNetworkId: " + physicalNetworkId);
}
final Long zoneId = pNtwk.getDataCenterId();
final Map<String, String> params = new HashMap<String, String>();
params.put("guid", "globodns-" + String.valueOf(zoneId));
params.put("zoneId", String.valueOf(zoneId));
params.put("name", Provider.GloboDns.getName());
params.put("url", url);
params.put("username", username);
params.put("password", password);
final Map<String, Object> hostDetails = new HashMap<String, Object>();
hostDetails.putAll(params);
Host host = Transaction.execute(new TransactionCallbackWithException<Host, CloudRuntimeException>() {
@Override
public Host doInTransaction(TransactionStatus status) throws CloudRuntimeException {
try {
GloboDnsResource resource = new GloboDnsResource();
resource.configure(Provider.GloboDns.getName(), hostDetails);
Host host = _resourceMgr.addHost(zoneId, resource, resource.getType(), params);
if (host == null) {
throw new CloudRuntimeException("Failed to add GloboDNS host");
}
// Validate username and password by logging in
SignInCommand cmd = new SignInCommand(username, password);
Answer answer = callCommand(cmd, zoneId);
if (answer == null || !answer.getResult()) {
// Could not sign in on GloboDNS
throw new ConfigurationException("Could not sign in on GloboDNS. Please verify URL, username and password.");
}
return host;
} catch (ConfigurationException e) {
throw new CloudRuntimeException(e);
}
}
});
return host;
}
}

View File

@ -0,0 +1,25 @@
// 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.element;
import com.cloud.host.Host;
import com.cloud.utils.component.PluggableService;
public interface GloboDnsElementService extends PluggableService {
public Host addGloboDnsHost(Long pNtwkId, String username, String password, String url);
}

View File

@ -0,0 +1,456 @@
/*
* 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 " + name + " 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;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.response;
import java.util.List;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.globo.globodns.client.model.Domain;
public class GloboDnsDomainListResponse extends Answer {
private List<Domain> domainList;
public GloboDnsDomainListResponse(Command command, List<Domain> domainList) {
super(command, true, null);
this.domainList = domainList;
}
public List<Domain> getDomainList() {
return domainList;
}
}

View File

@ -0,0 +1,20 @@
package com.globo.globodns.cloudstack.response;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.globo.globodns.client.model.Domain;
public class GloboDnsDomainResponse extends Answer {
private Domain domain;
public GloboDnsDomainResponse(Command command, Domain domain) {
super(command, true, null);
this.domain = domain;
}
public Domain getDomain() {
return domain;
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.response;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.globo.globodns.client.model.Export;
public class GloboDnsExportResponse extends Answer {
private Export export;
public GloboDnsExportResponse(Command command, Export export) {
super(command, true, null);
this.export = export;
}
public Export getExport() {
return export;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.response;
import java.util.List;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.globo.globodns.client.model.Record;
public class GloboDnsRecordListResponse extends Answer {
private List<Record> recordList;
public GloboDnsRecordListResponse(Command command, List<Record> recordList) {
super(command, true, null);
this.recordList = recordList;
}
public List<Record> getRecordList() {
return recordList;
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.response;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.globo.globodns.client.model.Record;
public class GloboDnsRecordResponse extends Answer {
private Record record;
public GloboDnsRecordResponse(Command command, Record record) {
super(command, true, null);
this.record = record;
}
public Record getRecord() {
return record;
}
}

View File

@ -0,0 +1,250 @@
package com.globo.globodns.cloudstack.element;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
import java.io.IOException;
import javax.inject.Inject;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.test.utils.SpringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.deploy.DeployDestination;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.host.Host.Type;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.network.Network;
import com.cloud.network.Network.Provider;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.PhysicalNetworkDao;
import com.cloud.resource.ResourceManager;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.UserVO;
import com.cloud.utils.component.ComponentContext;
import com.cloud.vm.NicProfile;
import com.cloud.vm.ReservationContext;
import com.cloud.vm.ReservationContextImpl;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineProfile;
import com.globo.globodns.cloudstack.commands.CreateOrUpdateRecordAndReverseCommand;
import com.globo.globodns.cloudstack.commands.RemoveRecordCommand;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class GloboDnsElementTest {
private static long zoneId = 5L;
private static long globoDnsHostId = 7L;
private static long domainId = 10L;
private AccountVO acct = null;
private UserVO user = null;
@Inject
DataCenterDao _datacenterDao;
@Inject
GloboDnsElement _globodnsElement;
@Inject
HostDao _hostDao;
@Inject
AgentManager _agentMgr;
@Inject
AccountManager _acctMgr;
@Before
public void setUp() throws Exception {
ComponentContext.initComponentsLifeCycle();
acct = new AccountVO(200L);
acct.setType(Account.ACCOUNT_TYPE_NORMAL);
acct.setAccountName("user");
acct.setDomainId(domainId);
user = new UserVO();
user.setUsername("user");
user.setAccountId(acct.getAccountId());
CallContext.register(user, acct);
when(_acctMgr.getSystemAccount()).thenReturn(this.acct);
when(_acctMgr.getSystemUser()).thenReturn(this.user);
}
@After
public void tearDown() throws Exception {
CallContext.unregister();
acct = null;
}
@Test(expected = InvalidParameterValueException.class)
public void testUpperCaseCharactersAreNotAllowed() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
Network network = mock(Network.class);
when(network.getDataCenterId()).thenReturn(zoneId);
when(network.getId()).thenReturn(1l);
NicProfile nic = new NicProfile();
VirtualMachineProfile vm = mock(VirtualMachineProfile.class);
when(vm.getHostName()).thenReturn("UPPERCASENAME");
when(vm.getType()).thenReturn(VirtualMachine.Type.User);
when(_datacenterDao.findById(zoneId)).thenReturn(mock(DataCenterVO.class));
DeployDestination dest = new DeployDestination();
ReservationContext context = new ReservationContextImpl(null, null, user);
_globodnsElement.prepare(network, nic, vm, dest, context);
}
@Test
public void testPrepareMethodCallGloboDnsToRegisterHostName() throws Exception {
Network network = mock(Network.class);
when(network.getDataCenterId()).thenReturn(zoneId);
when(network.getId()).thenReturn(1l);
NicProfile nic = new NicProfile();
nic.setIp4Address("10.11.12.13");
VirtualMachineProfile vm = mock(VirtualMachineProfile.class);
when(vm.getHostName()).thenReturn("vm-name");
when(vm.getType()).thenReturn(VirtualMachine.Type.User);
DataCenterVO dataCenterVO = mock(DataCenterVO.class);
when(dataCenterVO.getId()).thenReturn(zoneId);
when(_datacenterDao.findById(zoneId)).thenReturn(dataCenterVO);
DeployDestination dest = new DeployDestination();
ReservationContext context = new ReservationContextImpl(null, null, user);
HostVO hostVO = mock(HostVO.class);
when(hostVO.getId()).thenReturn(globoDnsHostId);
when(_hostDao.findByTypeNameAndZoneId(eq(zoneId), eq(Provider.GloboDns.getName()), eq(Type.L2Networking))).thenReturn(hostVO);
when(_agentMgr.easySend(eq(globoDnsHostId), isA(CreateOrUpdateRecordAndReverseCommand.class))).then(new org.mockito.stubbing.Answer<Answer>() {
@Override
public Answer answer(InvocationOnMock invocation) throws Throwable {
Command cmd = (Command)invocation.getArguments()[1];
return new Answer(cmd);
}
});
_globodnsElement.prepare(network, nic, vm, dest, context);
verify(_agentMgr, times(1)).easySend(eq(globoDnsHostId), isA(CreateOrUpdateRecordAndReverseCommand.class));
}
@Test
public void testReleaseMethodCallResource() throws Exception {
Network network = mock(Network.class);
when(network.getDataCenterId()).thenReturn(zoneId);
when(network.getId()).thenReturn(1l);
NicProfile nic = new NicProfile();
nic.setIp4Address("10.11.12.13");
VirtualMachineProfile vm = mock(VirtualMachineProfile.class);
when(vm.getHostName()).thenReturn("vm-name");
when(vm.getType()).thenReturn(VirtualMachine.Type.User);
DataCenterVO dataCenterVO = mock(DataCenterVO.class);
when(dataCenterVO.getId()).thenReturn(zoneId);
when(_datacenterDao.findById(zoneId)).thenReturn(dataCenterVO);
ReservationContext context = new ReservationContextImpl(null, null, user);
HostVO hostVO = mock(HostVO.class);
when(hostVO.getId()).thenReturn(globoDnsHostId);
when(_hostDao.findByTypeNameAndZoneId(eq(zoneId), eq(Provider.GloboDns.getName()), eq(Type.L2Networking))).thenReturn(hostVO);
when(_agentMgr.easySend(eq(globoDnsHostId), isA(RemoveRecordCommand.class))).then(new org.mockito.stubbing.Answer<Answer>() {
@Override
public Answer answer(InvocationOnMock invocation) throws Throwable {
Command cmd = (Command)invocation.getArguments()[1];
return new Answer(cmd);
}
});
_globodnsElement.release(network, nic, vm, context);
verify(_agentMgr, times(1)).easySend(eq(globoDnsHostId), isA(RemoveRecordCommand.class));
}
@Configuration
@ComponentScan(basePackageClasses = {GloboDnsElement.class}, includeFilters = {@Filter(value = TestConfiguration.Library.class, type = FilterType.CUSTOM)}, useDefaultFilters = false)
public static class TestConfiguration extends SpringUtils.CloudStackTestConfiguration {
@Bean
public HostDao hostDao() {
return mock(HostDao.class);
}
@Bean
public DataCenterDao dataCenterDao() {
return mock(DataCenterDao.class);
}
@Bean
public PhysicalNetworkDao physicalNetworkDao() {
return mock(PhysicalNetworkDao.class);
}
@Bean
public NetworkDao networkDao() {
return mock(NetworkDao.class);
}
@Bean
public ConfigurationDao configurationDao() {
return mock(ConfigurationDao.class);
}
@Bean
public AgentManager agentManager() {
return mock(AgentManager.class);
}
@Bean
public ResourceManager resourceManager() {
return mock(ResourceManager.class);
}
@Bean
public AccountManager accountManager() {
return mock(AccountManager.class);
}
public static class Library implements TypeFilter {
@Override
public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException {
ComponentScan cs = TestConfiguration.class.getAnnotation(ComponentScan.class);
return SpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs);
}
}
}
}

View File

@ -0,0 +1,447 @@
package com.globo.globodns.cloudstack.resource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.any;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.cloud.agent.api.Answer;
import com.globo.globodns.client.GloboDns;
import com.globo.globodns.client.api.DomainAPI;
import com.globo.globodns.client.api.ExportAPI;
import com.globo.globodns.client.api.RecordAPI;
import com.globo.globodns.client.model.Domain;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class GloboDnsResourceTest {
private GloboDnsResource _globoDnsResource;
private GloboDns _globoDnsApi;
private DomainAPI _domainApi;
private RecordAPI _recordApi;
private ExportAPI _exportApi;
private static final Long TEMPLATE_ID = 1l;
private static long sequenceId = 10l;
@Before
public void setUp() throws Exception {
// ComponentContext.initComponentsLifeCycle();
String name = "GloboDNS";
Map<String, Object> params = new HashMap<String, Object>();
params.put("zoneId", "1");
params.put("guid", "globodns");
params.put("name", name);
params.put("url", "http://example.com");
params.put("username", "username");
params.put("password", "password");
_globoDnsResource = new GloboDnsResource();
_globoDnsResource.configure(name, params);
_globoDnsApi = spy(_globoDnsResource._globoDns);
_globoDnsResource._globoDns = _globoDnsApi;
_domainApi = mock(DomainAPI.class);
when(_globoDnsApi.getDomainAPI()).thenReturn(_domainApi);
_recordApi = mock(RecordAPI.class);
when(_globoDnsApi.getRecordAPI()).thenReturn(_recordApi);
_exportApi = mock(ExportAPI.class);
when(_globoDnsApi.getExportAPI()).thenReturn(_exportApi);
}
@After
public void tearDown() throws Exception {
}
///////////////////////
// Auxiliary Methods //
///////////////////////
private Domain generateFakeDomain(String domainName, boolean reverse) {
Domain domain = new Domain();
domain.getDomainAttributes().setId(sequenceId++);
domain.getDomainAttributes().setName(domainName);
List<Domain> domainList = new ArrayList<Domain>();
domainList.add(domain);
if (reverse) {
when(_domainApi.listReverseByQuery(eq(domainName))).thenReturn(domainList);
} else {
when(_domainApi.listByQuery(eq(domainName))).thenReturn(domainList);
}
return domain;
}
private Record generateFakeRecord(Domain domain, String recordName, String recordContent, boolean reverse) {
Record record = new Record();
if (reverse) {
record.getTypePTRRecordAttributes().setName(recordName);
record.getTypePTRRecordAttributes().setContent(recordContent);
record.getTypePTRRecordAttributes().setDomainId(domain.getId());
record.getTypePTRRecordAttributes().setId(sequenceId++);
} else {
record.getTypeARecordAttributes().setName(recordName);
record.getTypeARecordAttributes().setContent(recordContent);
record.getTypeARecordAttributes().setDomainId(domain.getId());
record.getTypeARecordAttributes().setId(sequenceId++);
}
List<Record> recordList = new ArrayList<Record>();
recordList.add(record);
when(_recordApi.listByQuery(eq(domain.getId()), eq(recordName))).thenReturn(recordList);
return record;
}
/////////////////////////
// Create Domain tests //
/////////////////////////
@Test
public void testCreateDomainWithSuccessWhenDomainDoesntExistAndOverrideIsTrue() throws Exception {
String domainName = "domain.name.com";
Domain domain = new Domain();
domain.getDomainAttributes().setId(sequenceId++);
domain.getDomainAttributes().setName(domainName);
when(_domainApi.createDomain(eq(domain.getName()), eq(TEMPLATE_ID), eq("M"))).thenReturn(domain);
Answer answer = _globoDnsResource.execute(new CreateOrUpdateDomainCommand(domainName, TEMPLATE_ID));
assertNotNull(answer);
assertEquals(true, answer.getResult());
verify(_exportApi, times(1)).scheduleExport();
}
@Test
@SuppressWarnings("unused")
public void testCreateDomainWillSucceedWhenDomainAlreadyExistsAndOverrideIsFalse() throws Exception {
String domainName = "domain.name.com";
Domain domain = generateFakeDomain(domainName, false);
Answer answer = _globoDnsResource.execute(new CreateOrUpdateDomainCommand(domainName, TEMPLATE_ID));
assertNotNull(answer);
assertEquals(true, answer.getResult());
}
/////////////////////////
// Create Record tests //
/////////////////////////
@Test
@SuppressWarnings("unused")
public void testCreateRecordAndReverseWithSuccessWhenDomainExistsAndRecordDoesntExistAndOverrideIsTrue() throws Exception {
String recordName = "recordname";
String recordIp = "40.30.20.10";
String domainName = "domain.name.com";
String reverseDomainName = "20.30.40.in-addr.arpa";
String reverseRecordName = "10";
String reverseRecordContent = recordName + "." + domainName;
Domain domain = generateFakeDomain(domainName, false);
Record record = generateFakeRecord(domain, recordName, recordIp, false);
Domain reverseDomain = generateFakeDomain(reverseDomainName, true);
Record reverseRecord = generateFakeRecord(reverseDomain, reverseRecordName, reverseRecordContent, true);
when(_recordApi.createRecord(eq(domain.getId()), eq(recordName), eq(recordIp), eq("A"))).thenReturn(record);
when(_recordApi.createRecord(eq(reverseDomain.getId()), eq(reverseRecordName), eq(reverseRecordContent), eq("PTR"))).thenReturn(record);
Answer answer = _globoDnsResource.execute(new CreateOrUpdateRecordAndReverseCommand(recordName, recordIp, domainName, TEMPLATE_ID, true));
assertNotNull(answer);
assertEquals(true, answer.getResult());
verify(_exportApi, times(1)).scheduleExport();
}
@Test
@SuppressWarnings("unused")
public void testCreateRecordAndReverseWillFailWhenRecordAlreadyExistsAndOverrideIsFalse() throws Exception {
String recordName = "recordname";
String newIp = "40.30.20.10";
String oldIp = "50.40.30.20";
String domainName = "domain.name.com";
Domain domain = generateFakeDomain(domainName, false);
Record record = generateFakeRecord(domain, recordName, oldIp, false);
Answer answer = _globoDnsResource.execute(new CreateOrUpdateRecordAndReverseCommand(recordName, newIp, domainName, TEMPLATE_ID, false));
assertNotNull(answer);
assertEquals(false, answer.getResult());
}
@Test
@SuppressWarnings("unused")
public void testCreateRecordAndReverseWillFailWhenReverseRecordAlreadyExistsAndOverrideIsFalse() throws Exception {
String recordName = "recordname";
String recordIp = "40.30.20.10";
String domainName = "domain.name.com";
String reverseDomainName = "20.30.40.in-addr.arpa";
String reverseRecordName = "10";
Domain domain = generateFakeDomain(domainName, false);
Record record = generateFakeRecord(domain, recordName, recordIp, false);
Domain reverseDomain = generateFakeDomain(reverseDomainName, true);
Record reverseRecord = generateFakeRecord(reverseDomain, reverseRecordName, "X", true);
Answer answer = _globoDnsResource.execute(new CreateOrUpdateRecordAndReverseCommand(recordName, recordIp, domainName, TEMPLATE_ID, false));
assertNotNull(answer);
assertEquals(false, answer.getResult());
}
@Test
public void testCreateRecordAndReverseWhenDomainDoesNotExist() throws Exception {
String recordName = "recordname";
String recordIp = "40.30.20.10";
String domainName = "domain.name.com";
String reverseDomainName = "20.30.40.in-addr.arpa";
String reverseRecordName = "10";
String reverseRecordContent = recordName + "." + domainName;
Domain domain = new Domain();
domain.getDomainAttributes().setId(sequenceId++);
Domain reverseDomain = new Domain();
reverseDomain.getDomainAttributes().setId(sequenceId++);
Record record = new Record();
when(_domainApi.listByQuery(domainName)).thenReturn(new ArrayList<Domain>());
when(_domainApi.createDomain(eq(domainName), eq(TEMPLATE_ID), eq("M"))).thenReturn(domain);
when(_recordApi.createRecord(eq(domain.getId()), eq(recordName), eq(recordIp), eq("A"))).thenReturn(record);
when(_domainApi.createReverseDomain(eq(reverseDomainName), eq(TEMPLATE_ID), eq("M"))).thenReturn(reverseDomain);
when(_recordApi.createRecord(eq(reverseDomain.getId()), eq(reverseRecordName), eq(reverseRecordContent), eq("PTR"))).thenReturn(record);
Answer answer = _globoDnsResource.execute(new CreateOrUpdateRecordAndReverseCommand(recordName, recordIp, domainName, TEMPLATE_ID, true));
assertNotNull(answer);
assertEquals(true, answer.getResult());
verify(_exportApi, times(1)).scheduleExport();
}
@Test
public void testCreateRecordAndReverseWhenDomainDoesNotExistAndOverrideIsFalse() throws Exception {
String recordName = "recordname";
String recordIp = "40.30.20.10";
String domainName = "domain.name.com";
String reverseDomainName = "20.30.40.in-addr.arpa";
String reverseRecordName = "10";
String reverseRecordContent = recordName + "." + domainName;
Domain domain = new Domain();
domain.getDomainAttributes().setId(sequenceId++);
Domain reverseDomain = new Domain();
reverseDomain.getDomainAttributes().setId(sequenceId++);
Record record = new Record();
when(_domainApi.listByQuery(domainName)).thenReturn(new ArrayList<Domain>());
when(_domainApi.createDomain(eq(domainName), eq(TEMPLATE_ID), eq("M"))).thenReturn(domain);
when(_recordApi.createRecord(eq(domain.getId()), eq(recordName), eq(recordIp), eq("A"))).thenReturn(record);
when(_domainApi.createReverseDomain(eq(reverseDomainName), eq(TEMPLATE_ID), eq("M"))).thenReturn(reverseDomain);
when(_recordApi.createRecord(eq(reverseDomain.getId()), eq(reverseRecordName), eq(reverseRecordContent), eq("PTR"))).thenReturn(record);
Answer answer = _globoDnsResource.execute(new CreateOrUpdateRecordAndReverseCommand(recordName, recordIp, domainName, TEMPLATE_ID, false));
assertNotNull(answer);
assertEquals(true, answer.getResult());
verify(_exportApi, times(1)).scheduleExport();
}
/////////////////////////
// Update Record tests //
/////////////////////////
@Test
public void testUpdateRecordAndReverseWhenDomainExistsAndOverrideIsTrue() throws Exception {
String recordName = "recordname";
String oldRecordIp = "40.30.20.10";
String newRecordIp = "50.40.30.20";
String domainName = "domain.name.com";
String reverseDomainName = "30.40.50.in-addr.arpa";
String reverseRecordName = "20";
String reverseRecordContent = recordName + "." + domainName;
Domain domain = generateFakeDomain(domainName, false);
Record record = generateFakeRecord(domain, recordName, oldRecordIp, false);
Domain reverseDomain = generateFakeDomain(reverseDomainName, true);
Record reverseRecord = generateFakeRecord(reverseDomain, reverseRecordName, "X", true);
Answer answer = _globoDnsResource.execute(new CreateOrUpdateRecordAndReverseCommand(recordName, newRecordIp, domainName, TEMPLATE_ID, true));
// ensure calls in sequence to ensure this call are the only ones.
InOrder inOrder = inOrder(_recordApi);
inOrder.verify(_recordApi, times(1)).updateRecord(eq(record.getId()), eq(domain.getId()), eq(recordName), eq(newRecordIp));
inOrder.verify(_recordApi, times(1)).updateRecord(eq(reverseRecord.getId()), eq(reverseDomain.getId()), eq(reverseRecordName), eq(reverseRecordContent));
assertNotNull(answer);
assertEquals(true, answer.getResult());
verify(_exportApi, times(1)).scheduleExport();
}
/////////////////////////
// Remove Record tests //
/////////////////////////
@Test
public void testRemoveRecordWhenRecordExists() throws Exception {
String recordName = "recordname";
String recordIp = "40.30.20.10";
String domainName = "domain.name.com";
String reverseDomainName = "20.30.40.in-addr.arpa";
String reverseRecordName = "10";
String reverseRecordContent = recordName + "." + domainName;
Domain domain = generateFakeDomain(domainName, false);
Record record = generateFakeRecord(domain, recordName, recordIp, false);
Domain reverseDomain = generateFakeDomain(reverseDomainName, true);
Record reverseRecord = generateFakeRecord(reverseDomain, reverseRecordName, reverseRecordContent, true);
Answer answer = _globoDnsResource.execute(new RemoveRecordCommand(recordName, recordIp, domainName, true));
assertNotNull(answer);
assertEquals(true, answer.getResult());
verify(_recordApi, times(1)).removeRecord(eq(record.getId()));
verify(_recordApi, times(1)).removeRecord(eq(reverseRecord.getId()));
verify(_exportApi, times(1)).scheduleExport();
}
@Test
public void testRemoveRecordWithSuccessAndReverseRecordNotRemovedWhenReverseRecordExistsWithDifferentValueAndOverrideIsFalse() throws Exception {
String recordName = "recordname";
String recordIp = "40.30.20.10";
String domainName = "domain.name.com";
String reverseDomainName = "20.30.40.in-addr.arpa";
String reverseRecordName = "10";
Domain domain = generateFakeDomain(domainName, false);
Record record = generateFakeRecord(domain, recordName, recordIp, false);
Domain reverseDomain = generateFakeDomain(reverseDomainName, true);
Record reverseRecord = generateFakeRecord(reverseDomain, reverseRecordName, "X", true);
Answer answer = _globoDnsResource.execute(new RemoveRecordCommand(recordName, recordIp, domainName, false));
assertEquals(true, answer.getResult());
verify(_recordApi, times(1)).removeRecord(eq(record.getId()));
verify(_recordApi, never()).removeRecord(eq(reverseRecord.getId()));
verify(_exportApi, times(1)).scheduleExport();
}
@Test
public void testRemoveReverseRecordButNotRemoveRecordWhenRecordExistsWithDifferentValueAndOverrideIsFalse() throws Exception {
String recordName = "recordname";
String recordIp = "40.30.20.10";
String domainName = "domain.name.com";
String reverseDomainName = "20.30.40.in-addr.arpa";
String reverseRecordName = "10";
String reverseRecordContent = recordName + "." + domainName;
Domain domain = generateFakeDomain(domainName, false);
Record record = generateFakeRecord(domain, recordName, "X", false);
Domain reverseDomain = generateFakeDomain(reverseDomainName, true);
Record reverseRecord = generateFakeRecord(reverseDomain, reverseRecordName, reverseRecordContent, true);
Answer answer = _globoDnsResource.execute(new RemoveRecordCommand(recordName, recordIp, domainName, false));
assertEquals(true, answer.getResult());
verify(_recordApi, never()).removeRecord(eq(record.getId()));
verify(_recordApi, times(1)).removeRecord(eq(reverseRecord.getId()));
verify(_exportApi, times(1)).scheduleExport();
}
/////////////////////////
// Remove Domain tests //
/////////////////////////
@Test
public void testRemoveDomainWithSuccessButDomainKeptWhenDomainExistsAndThereAreRecordsAndOverrideIsFalse() throws Exception {
String recordName = "recordname";
String recordIp = "40.30.20.10";
String domainName = "domain.name.com";
Domain domain = generateFakeDomain(domainName, false);
Record record = generateFakeRecord(domain, recordName, "X", false);
when(_recordApi.listAll(domain.getId())).thenReturn(Arrays.asList(record));
Answer answer = _globoDnsResource.execute(new RemoveRecordCommand(recordName, recordIp, domainName, false));
assertEquals(true, answer.getResult());
verify(_domainApi, never()).removeDomain(any(Long.class));
verify(_exportApi, never()).scheduleExport();
}
@Test
public void testRemoveDomainWithSuccessWhenDomainExistsAndThereAreOnlyNSRecordsAndOverrideIsFalse() throws Exception {
String domainName = "domain.name.com";
Domain domain = generateFakeDomain(domainName, false);
List<Record> recordList = new ArrayList<Record>();
for (int i = 0; i < 10; i++) {
Record record = new Record();
record.getTypeNSRecordAttributes().setDomainId(domain.getId());
record.getTypeNSRecordAttributes().setId(sequenceId++);
record.getTypeNSRecordAttributes().setType("NS");
recordList.add(record);
}
when(_recordApi.listAll(domain.getId())).thenReturn(recordList);
Answer answer = _globoDnsResource.execute(new RemoveDomainCommand(domainName, false));
assertEquals(true, answer.getResult());
verify(_domainApi, times(1)).removeDomain(eq(domain.getId()));
verify(_exportApi, times(1)).scheduleExport();
}
@Test
public void testRemoveDomainWithSuccessWhenDomainExistsAndThereAreRecordsAndOverrideIsTrue() throws Exception {
String domainName = "domain.name.com";
Domain domain = generateFakeDomain(domainName, false);
List<Record> recordList = new ArrayList<Record>();
for (int i = 0; i < 10; i++) {
Record record = new Record();
record.getTypeNSRecordAttributes().setDomainId(domain.getId());
record.getTypeNSRecordAttributes().setId(sequenceId++);
record.getTypeNSRecordAttributes().setType(new String[] {"A", "NS", "PTR"}[i % 3]);
recordList.add(record);
}
when(_recordApi.listAll(domain.getId())).thenReturn(recordList);
Answer answer = _globoDnsResource.execute(new RemoveDomainCommand(domainName, true));
assertEquals(true, answer.getResult());
verify(_domainApi, times(1)).removeDomain(eq(domain.getId()));
verify(_exportApi, times(1)).scheduleExport();
}
}

View File

@ -0,0 +1,75 @@
# 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.
# management server clustering parameters, change cluster.node.IP to the machine IP address
# in which the management server(Tomcat) is running
cluster.node.IP=127.0.0.1
cluster.servlet.port=9090
region.id=1
# CloudStack database settings
db.cloud.username=cloud
db.cloud.password=cloud
db.root.password=
db.cloud.host=localhost
db.cloud.port=3306
db.cloud.name=cloud
# CloudStack database tuning parameters
db.cloud.maxActive=250
db.cloud.maxIdle=30
db.cloud.maxWait=10000
db.cloud.autoReconnect=true
db.cloud.validationQuery=SELECT 1
db.cloud.testOnBorrow=true
db.cloud.testWhileIdle=true
db.cloud.timeBetweenEvictionRunsMillis=40000
db.cloud.minEvictableIdleTimeMillis=240000
db.cloud.poolPreparedStatements=false
db.cloud.url.params=prepStmtCacheSize=517&cachePrepStmts=true&prepStmtCacheSqlLimit=4096
# usage database settings
db.usage.username=cloud
db.usage.password=cloud
db.usage.host=localhost
db.usage.port=3306
db.usage.name=cloud_usage
# usage database tuning parameters
db.usage.maxActive=100
db.usage.maxIdle=30
db.usage.maxWait=10000
db.usage.autoReconnect=true
# awsapi database settings
db.awsapi.username=cloud
db.awsapi.password=cloud
db.awsapi.host=localhost
db.awsapi.port=3306
db.awsapi.name=cloudbridge
# Simulator database settings
db.simulator.username=cloud
db.simulator.password=cloud
db.simulator.host=localhost
db.simulator.port=3306
db.simulator.name=simulator
db.simulator.maxActive=250
db.simulator.maxIdle=30
db.simulator.maxWait=10000
db.simulator.autoReconnect=true

View File

@ -0,0 +1,16 @@
# Define the root logger with appender file
#log = /var/log/log4j
#log4j.rootLogger = DEBUG, FILE
log4j.rootLogger = DEBUG, stdout
# File appender
#log4j.appender.FILE=org.apache.log4j.FileAppender
#log4j.appender.FILE.File=${log}/log.out
#log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
#log4j.appender.FILE.layout.conversionPattern=%m%n
# Stdout appender
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

View File

@ -88,6 +88,7 @@
<module>alert-handlers/syslog-alerts</module>
<module>network-elements/internal-loadbalancer</module>
<module>network-elements/vxlan</module>
<module>network-elements/globodns</module>
</modules>
<dependencies>

View File

@ -7424,7 +7424,211 @@
}
}
}
}
},
// GloboDns provider detail view
GloboDns: {
isMaximized: true,
type: 'detailView',
id: 'globoDnsProvider',
label: 'GloboDNS',
tabs: {
details: {
title: 'label.details',
fields: [{
name: {
label: 'label.name'
}
}, {
state: {
label: 'label.state'
}
}],
dataProvider: function(args) {
refreshNspData("GloboDns");
var providerObj;
$(nspHardcodingArray).each(function() {
if (this.id == "GloboDns") {
providerObj = this;
return false; //break each loop
}
});
args.response.success({
data: providerObj,
actionFilter: networkProviderActionFilter('GloboDns')
});
}
},
},
actions: {
add: {
label: 'GloboDNS Configuration',
createForm: {
title: 'GloboDNS Configuration',
preFilter: function(args) {},
fields: {
username: {
label: 'Username',
validation: {
required: true
}
},
password: {
label: 'Password',
isPassword: true,
validation: {
required: true
}
},
url: {
label: 'URL',
validation: {
required: true
}
}
}
},
action: function(args) {
if (nspMap["GloboDns"] == null) {
$.ajax({
url: createURL("addNetworkServiceProvider&name=GloboDns&physicalnetworkid=" + selectedPhysicalNetworkObj.id),
dataType: "json",
async: true,
success: function(json) {
var jobId = json.addnetworkserviceproviderresponse.jobid;
var addGloboDnsProviderIntervalID = setInterval(function() {
$.ajax({
url: createURL("queryAsyncJobResult&jobId=" + jobId),
dataType: "json",
success: function(json) {
var result = json.queryasyncjobresultresponse;
if (result.jobstatus == 0) {
return; //Job has not completed
} else {
clearInterval(addGloboDnsProviderIntervalID);
if (result.jobstatus == 1) {
nspMap["GloboDns"] = json.queryasyncjobresultresponse.jobresult.networkserviceprovider;
addGloboDnsHost(args, selectedPhysicalNetworkObj, "addGloboDnsHost", "addglobodnshostresponse");
} else if (result.jobstatus == 2) {
alert("addNetworkServiceProvider&name=GloboDns failed. Error: " + _s(result.jobresult.errortext));
}
}
},
error: function(XMLHttpResponse) {
var errorMsg = parseXMLHttpResponse(XMLHttpResponse);
alert("addNetworkServiceProvider&name=GloboDns failed. Error: " + errorMsg);
}
});
}, g_queryAsyncJobResultInterval);
}
});
} else {
addGloboDnsHost(args, selectedPhysicalNetworkObj, "addGloboDnsHost", "addglobodnshostresponse");
}
},
messages: {
notification: function(args) {
return 'Add GloboDNS';
}
},
notification: {
poll: pollAsyncJobResult
}
},
enable: {
label: 'label.enable.provider',
action: function(args) {
$.ajax({
url: createURL("updateNetworkServiceProvider&id=" + nspMap["GloboDns"].id + "&state=Enabled"),
dataType: "json",
success: function(json) {
var jid = json.updatenetworkserviceproviderresponse.jobid;
args.response.success({
_custom: {
jobId: jid,
getUpdatedItem: function(json) {
$(window).trigger('cloudStack.fullRefresh');
}
}
});
}
});
},
messages: {
confirm: function(args) {
return 'message.confirm.enable.provider';
},
notification: function() {
return 'label.enable.provider';
}
},
notification: {
poll: pollAsyncJobResult
}
},
disable: {
label: 'label.disable.provider',
action: function(args) {
$.ajax({
url: createURL("updateNetworkServiceProvider&id=" + nspMap["GloboDns"].id + "&state=Disabled"),
dataType: "json",
success: function(json) {
var jid = json.updatenetworkserviceproviderresponse.jobid;
args.response.success({
_custom: {
jobId: jid,
getUpdatedItem: function(json) {
$(window).trigger('cloudStack.fullRefresh');
}
}
});
}
});
},
messages: {
confirm: function(args) {
return 'message.confirm.disable.provider';
},
notification: function() {
return 'label.disable.provider';
}
},
notification: {
poll: pollAsyncJobResult
}
},
destroy: {
label: 'label.shutdown.provider',
action: function(args) {
$.ajax({
url: createURL("deleteNetworkServiceProvider&id=" + nspMap["GloboDns"].id),
dataType: "json",
success: function(json) {
var jid = json.deletenetworkserviceproviderresponse.jobid;
args.response.success({
_custom: {
jobId: jid
}
});
$(window).trigger('cloudStack.fullRefresh');
}
});
},
messages: {
confirm: function(args) {
return 'message.confirm.shutdown.provider';
},
notification: function(args) {
return 'label.shutdown.provider';
}
},
notification: {
poll: pollAsyncJobResult
}
}
}
}
}
}
},
@ -19809,7 +20013,30 @@
});
}
});
}
}
function addGloboDnsHost(args, physicalNetworkObj, apiCmd, apiCmdRes) {
var array1 = [];
array1.push("&physicalnetworkid=" + physicalNetworkObj.id);
array1.push("&username=" + todb(args.data.username));
array1.push("&password=" + todb(args.data.password));
array1.push("&url=" + todb(args.data.url));
$.ajax({
url: createURL(apiCmd + array1.join("")),
dataType: "json",
type: "POST",
success: function(json) {
var jid = json[apiCmdRes].jobid;
args.response.success({
_custom: {
jobId: jid,
}
});
}
});
}
var afterCreateZonePhysicalNetworkTrafficTypes = function (args, newZoneObj, newPhysicalnetwork) {
$.ajax({
@ -20495,6 +20722,9 @@
case "NuageVsp":
nspMap["nuageVsp"] = items[i];
break;
case "GloboDns":
nspMap["GloboDns"] = items[i];
break;
}
}
}
@ -20598,6 +20828,11 @@
name: 'Palo Alto',
state: nspMap.pa ? nspMap.pa.state: 'Disabled'
});
nspHardcodingArray.push({
id: 'GloboDns',
name: 'GloboDNS',
state: nspMap.GloboDns ? nspMap.GloboDns.state : 'Disabled'
});
}
if ($.grep(nspHardcodingArray, function(e) { return e.id == 'Ovs'; }).length == 0 ) {