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.ya...@shapeblue.com> Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/233445ed Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/233445ed Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/233445ed Branch: refs/heads/saml2 Commit: 233445ed68e3f903367e14a277ec2a5de9597b52 Parents: 7ada4ad Author: Daniel Vega <daniel.sim...@corp.globo.com> Authored: Wed Aug 20 15:49:40 2014 -0300 Committer: Rohit Yadav <rohit.ya...@shapeblue.com> Committed: Thu Aug 21 11:54:44 2014 +0200 ---------------------------------------------------------------------- api/src/com/cloud/network/Network.java | 2 + .../network/ExternalNetworkDeviceManager.java | 1 + client/pom.xml | 5 + client/tomcatconf/commands.properties.in | 3 + plugins/network-elements/globodns/pom.xml | 37 ++ .../cloudstack/globodns/module.properties | 18 + .../globodns/spring-globodns-context.xml | 22 + .../cloudstack/api/AddGloboDnsHostCmd.java | 124 +++++ .../commands/CreateOrUpdateDomainCommand.java | 44 ++ .../CreateOrUpdateRecordAndReverseCommand.java | 66 +++ .../commands/RemoveDomainCommand.java | 44 ++ .../commands/RemoveRecordCommand.java | 59 +++ .../cloudstack/commands/SignInCommand.java | 44 ++ .../cloudstack/element/GloboDnsElement.java | 388 ++++++++++++++++ .../element/GloboDnsElementService.java | 25 + .../cloudstack/resource/GloboDnsResource.java | 456 +++++++++++++++++++ .../response/GloboDnsDomainListResponse.java | 38 ++ .../response/GloboDnsDomainResponse.java | 20 + .../response/GloboDnsExportResponse.java | 36 ++ .../response/GloboDnsRecordListResponse.java | 38 ++ .../response/GloboDnsRecordResponse.java | 36 ++ .../cloudstack/element/GloboDnsElementTest.java | 250 ++++++++++ .../resource/GloboDnsResourceTest.java | 447 ++++++++++++++++++ .../globodns/test/resources/db.properties | 75 +++ .../globodns/test/resources/log4j.properties | 16 + plugins/pom.xml | 1 + ui/scripts/system.js | 239 +++++++++- 27 files changed, 2532 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/api/src/com/cloud/network/Network.java ---------------------------------------------------------------------- diff --git a/api/src/com/cloud/network/Network.java b/api/src/com/cloud/network/Network.java index 55502df..c5a9bf2 100644 --- a/api/src/com/cloud/network/Network.java +++ b/api/src/com/cloud/network/Network.java @@ -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; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/api/src/org/apache/cloudstack/network/ExternalNetworkDeviceManager.java ---------------------------------------------------------------------- diff --git a/api/src/org/apache/cloudstack/network/ExternalNetworkDeviceManager.java b/api/src/org/apache/cloudstack/network/ExternalNetworkDeviceManager.java index f38e9e6..b349356 100644 --- a/api/src/org/apache/cloudstack/network/ExternalNetworkDeviceManager.java +++ b/api/src/org/apache/cloudstack/network/ExternalNetworkDeviceManager.java @@ -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; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/client/pom.xml ---------------------------------------------------------------------- diff --git a/client/pom.xml b/client/pom.xml index d87fc45..dccf18d 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -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> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/client/tomcatconf/commands.properties.in ---------------------------------------------------------------------- diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index 3191bbf..18ac2bf 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -763,3 +763,6 @@ createServiceInstance=1 addOpenDaylightController=1 deleteOpenDaylightController=1 listOpenDaylightControllers=1 + +### GloboDNS commands +addGloboDnsHost=1 http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/pom.xml ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/pom.xml b/plugins/network-elements/globodns/pom.xml new file mode 100644 index 0000000..0f17bba --- /dev/null +++ b/plugins/network-elements/globodns/pom.xml @@ -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> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/resources/META-INF/cloudstack/globodns/module.properties ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/resources/META-INF/cloudstack/globodns/module.properties b/plugins/network-elements/globodns/resources/META-INF/cloudstack/globodns/module.properties new file mode 100644 index 0000000..6c74bd2 --- /dev/null +++ b/plugins/network-elements/globodns/resources/META-INF/cloudstack/globodns/module.properties @@ -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 \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/resources/META-INF/cloudstack/globodns/spring-globodns-context.xml ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/resources/META-INF/cloudstack/globodns/spring-globodns-context.xml b/plugins/network-elements/globodns/resources/META-INF/cloudstack/globodns/spring-globodns-context.xml new file mode 100644 index 0000000..7e2e809 --- /dev/null +++ b/plugins/network-elements/globodns/resources/META-INF/cloudstack/globodns/spring-globodns-context.xml @@ -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> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/api/AddGloboDnsHostCmd.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/api/AddGloboDnsHostCmd.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/api/AddGloboDnsHostCmd.java new file mode 100644 index 0000000..1694a32 --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/api/AddGloboDnsHostCmd.java @@ -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"; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/CreateOrUpdateDomainCommand.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/CreateOrUpdateDomainCommand.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/CreateOrUpdateDomainCommand.java new file mode 100644 index 0000000..83564cb --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/CreateOrUpdateDomainCommand.java @@ -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; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/CreateOrUpdateRecordAndReverseCommand.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/CreateOrUpdateRecordAndReverseCommand.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/CreateOrUpdateRecordAndReverseCommand.java new file mode 100644 index 0000000..a5bafe9 --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/CreateOrUpdateRecordAndReverseCommand.java @@ -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; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/RemoveDomainCommand.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/RemoveDomainCommand.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/RemoveDomainCommand.java new file mode 100644 index 0000000..dffdce1 --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/RemoveDomainCommand.java @@ -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; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/RemoveRecordCommand.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/RemoveRecordCommand.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/RemoveRecordCommand.java new file mode 100644 index 0000000..3479b26 --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/RemoveRecordCommand.java @@ -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; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/SignInCommand.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/SignInCommand.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/SignInCommand.java new file mode 100644 index 0000000..86f82db --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/commands/SignInCommand.java @@ -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; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/element/GloboDnsElement.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/element/GloboDnsElement.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/element/GloboDnsElement.java new file mode 100644 index 0000000..c5a184c --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/element/GloboDnsElement.java @@ -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; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/element/GloboDnsElementService.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/element/GloboDnsElementService.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/element/GloboDnsElementService.java new file mode 100644 index 0000000..c141ad1 --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/element/GloboDnsElementService.java @@ -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); +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/resource/GloboDnsResource.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/resource/GloboDnsResource.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/resource/GloboDnsResource.java new file mode 100644 index 0000000..c167845 --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/resource/GloboDnsResource.java @@ -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; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsDomainListResponse.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsDomainListResponse.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsDomainListResponse.java new file mode 100644 index 0000000..7bddab8 --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsDomainListResponse.java @@ -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; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsDomainResponse.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsDomainResponse.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsDomainResponse.java new file mode 100644 index 0000000..23b8f3f --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsDomainResponse.java @@ -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; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsExportResponse.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsExportResponse.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsExportResponse.java new file mode 100644 index 0000000..a51f540 --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsExportResponse.java @@ -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; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsRecordListResponse.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsRecordListResponse.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsRecordListResponse.java new file mode 100644 index 0000000..edd1bb7 --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsRecordListResponse.java @@ -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; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsRecordResponse.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsRecordResponse.java b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsRecordResponse.java new file mode 100644 index 0000000..9236d11 --- /dev/null +++ b/plugins/network-elements/globodns/src/com/globo/globodns/cloudstack/response/GloboDnsRecordResponse.java @@ -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; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/233445ed/plugins/network-elements/globodns/test/com/globo/globodns/cloudstack/element/GloboDnsElementTest.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/globodns/test/com/globo/globodns/cloudstack/element/GloboDnsElementTest.java b/plugins/network-elements/globodns/test/com/globo/globodns/cloudstack/element/GloboDnsElementTest.java new file mode 100644 index 0000000..7c05622 --- /dev/null +++ b/plugins/network-elements/globodns/test/com/globo/globodns/cloudstack/element/GloboDnsElementTest.java @@ -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); + } + } + } +}