/*
 * 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 org.jclouds.openstack.nova.v2_0.compute.functions;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;

import java.net.URI;
import java.util.concurrent.atomic.AtomicReference;

import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.NodeMetadata.Status;
import org.jclouds.compute.domain.NodeMetadataBuilder;
import org.jclouds.domain.Location;
import org.jclouds.domain.LocationBuilder;
import org.jclouds.domain.LocationScope;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.openstack.nova.v2_0.compute.options.NodeAndNovaTemplateOptions;
import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions;
import org.jclouds.openstack.nova.v2_0.internal.BaseNovaComputeServiceExpectTest;
import org.testng.annotations.Test;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Atomics;

/**
 * Tests the compute service abstraction of the nova api.
 */
@Test(groups = "unit", testName = "AllocateAndAddFloatingIpToNodeTest")
public class AllocateAndAddFloatingIpToNodeExpectTest extends BaseNovaComputeServiceExpectTest {
   final Location provider = new LocationBuilder().scope(LocationScope.PROVIDER).id("openstack-nova").description(
            "openstack-nova").build();
   final Location region = new LocationBuilder().id("az-1.region-a.geo-1").description("az-1.region-a.geo-1").scope(
            LocationScope.REGION).parent(provider).build();
   final Location host = new LocationBuilder().scope(LocationScope.HOST).id("hostId").description("hostId")
            .parent(region).build();
   final NodeMetadata node = new NodeMetadataBuilder().id("az-1.region-a.geo-1/71592").providerId("71592").location(
            host).name("Server 71592").status(Status.RUNNING).privateAddresses(ImmutableSet.of("10.4.27.237"))
            .credentials(LoginCredentials.builder().password("foo").build()).build();
   final NovaTemplateOptions options = NovaTemplateOptions.Builder.autoAssignFloatingIp(false);

   HttpRequest createFloatingIP = HttpRequest.builder().method("POST").endpoint(
            URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-floating-ips")).headers(
            ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
                     authToken).build()).payload(payloadFromStringWithContentType("{}", "application/json")).build();

   HttpResponse addFloatingIPResponse = HttpResponse.builder().statusCode(200).build();

   public void testAllocateWhenAllocationReturnsIpIsAddedToServerAndUpdatesNodeMetadataButSavesCredentials() throws Exception {
      HttpResponse createFloatingIPResponse = HttpResponse.builder().statusCode(200).payload(
               payloadFromResource("/floatingip_details.json")).build();

      HttpRequest listAZs = HttpRequest.builder().method("GET").endpoint(
              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-availability-zone")).headers(
              ImmutableMultimap.<String, String> builder().put("Accept", "application/json")
                      .put("X-Auth-Token", authToken)
                      .build())
              .build();

      HttpResponse listAZsResponseForUnassigned = HttpResponse.builder().statusCode(200).payload(
              payloadFromResource("/availability_zone_list.json")).build();

      HttpRequest addFloatingIPRequest = addFloatingIPForAddress("10.0.0.3");

      AllocateAndAddFloatingIpToNode fn = requestsSendResponses(
               ImmutableMap.<HttpRequest, HttpResponse> builder()
                       .put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess)
                       .put(listAZs, listAZsResponseForUnassigned)
                       .put(extensionsOfNovaRequest, extensionsOfNovaResponse)
                       .put(createFloatingIP, createFloatingIPResponse)
                       .put(addFloatingIPRequest, addFloatingIPResponse).build())
              .getContext().utils().injector()
              .getInstance(AllocateAndAddFloatingIpToNode.class);

      AtomicReference<NodeMetadata> nodeRef = Atomics.newReference(node);
      AtomicReference<NovaTemplateOptions> optionsRef = Atomics.newReference(options);
      AtomicReference<NodeAndNovaTemplateOptions> nodeNovaRef = NodeAndNovaTemplateOptions.newAtomicReference(nodeRef, optionsRef);

      fn.apply(nodeNovaRef);
      NodeMetadata node1 = nodeRef.get();
      assertNotNull(node1);
      assertNotNull(optionsRef.get());
      assertEquals(node1.getPublicAddresses(), ImmutableSet.of("10.0.0.3"));
      assertEquals(node1.getCredentials(), node.getCredentials());

   }

   private HttpRequest addFloatingIPForAddress(String address) {
      HttpRequest addFloatingIPRequest = HttpRequest.builder().method("POST").endpoint(
               URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/servers/71592/action"))
               .headers(
                        ImmutableMultimap.<String, String> builder().put("Accept", "application/json")
                                 .put("X-Auth-Token", authToken).build()).payload(
                        payloadFromStringWithContentType("{\"addFloatingIp\":{\"address\":\"" + address + "\"}}",
                                 "application/json")).build();
      return addFloatingIPRequest;
   }

   public void testAllocateWhenAllocationFailsOn400LookupUnusedIpAddToServerAndUpdatesNodeMetadata() throws Exception {
      HttpResponse createFloatingIPResponse = HttpResponse
               .builder()
               .statusCode(400)
               .payload(
                        payloadFromStringWithContentType(
                                 "{\"badRequest\": {\"message\": \"AddressLimitExceeded: Address quota exceeded. You cannot create any more addresses\", \"code\": 400}}",
                                 "application/json")).build();

      HttpRequest listAZs = HttpRequest.builder().method("GET").endpoint(
              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-availability-zone")).headers(
              ImmutableMultimap.<String, String> builder().put("Accept", "application/json")
                      .put("X-Auth-Token", authToken)
                      .build())
              .build();

      HttpResponse listAZsResponseForUnassigned = HttpResponse.builder().statusCode(200).payload(
              payloadFromResource("/availability_zone_list.json")).build();

      HttpRequest list = HttpRequest.builder().method("GET").endpoint(
               URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-floating-ips")).headers(
               ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
                        authToken).build()).build();

      HttpResponse listResponseForUnassigned = HttpResponse.builder().statusCode(200).payload(
               payloadFromResource("/floatingip_list.json")).build();

      HttpRequest addFloatingIPRequest = addFloatingIPForAddress("10.0.0.5");

      AllocateAndAddFloatingIpToNode fn = requestsSendResponses(
               ImmutableMap.<HttpRequest, HttpResponse> builder()
                       .put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess)
                       .put(listAZs, listAZsResponseForUnassigned)
                       .put(extensionsOfNovaRequest, extensionsOfNovaResponse)
                       .put(createFloatingIP, createFloatingIPResponse)
                       .put(addFloatingIPRequest, addFloatingIPResponse)
                       .put(list, listResponseForUnassigned)
                       .build())
              .getContext().utils().injector()
              .getInstance(AllocateAndAddFloatingIpToNode.class);

      AtomicReference<NodeMetadata> nodeRef = Atomics.newReference(node);
      AtomicReference<NovaTemplateOptions> optionsRef = Atomics.newReference(options);
      AtomicReference<NodeAndNovaTemplateOptions> nodeNovaRef = NodeAndNovaTemplateOptions.newAtomicReference(nodeRef, optionsRef);

      fn.apply(nodeNovaRef);
      NodeMetadata node1 = nodeRef.get();
      assertNotNull(node1);
      assertNotNull(optionsRef.get());
      assertEquals(node1.getPublicAddresses(), ImmutableSet.of("10.0.0.5"));
   }

   public void testAllocateWhenAllocationFailsOn404LookupUnusedIpAddToServerAndUpdatesNodeMetadata() throws Exception {
      HttpResponse createFloatingIPResponse = HttpResponse
               .builder()
               .statusCode(404)
               .payload(
                        payloadFromStringWithContentType(
                                 "{\"badRequest\": {\"message\": \"AddressLimitExceeded: Address quota exceeded. You cannot create any more addresses\", \"code\": 404}}",
                                 "application/json")).build();

      HttpRequest listAZs = HttpRequest.builder().method("GET").endpoint(
              URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-availability-zone")).headers(
              ImmutableMultimap.<String, String> builder().put("Accept", "application/json")
                      .put("X-Auth-Token", authToken)
                      .build())
              .build();

      HttpResponse listAZsResponseForUnassigned = HttpResponse.builder().statusCode(200).payload(
              payloadFromResource("/availability_zone_list.json")).build();

      HttpRequest list = HttpRequest.builder().method("GET").endpoint(
               URI.create("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v2/3456/os-floating-ips")).headers(
               ImmutableMultimap.<String, String> builder().put("Accept", "application/json").put("X-Auth-Token",
                        authToken).build()).build();

      HttpResponse listResponseForUnassigned = HttpResponse.builder().statusCode(200).payload(
               payloadFromResource("/floatingip_list.json")).build();

      HttpRequest addFloatingIPRequest = addFloatingIPForAddress("10.0.0.5");

      AllocateAndAddFloatingIpToNode fn = requestsSendResponses(
               ImmutableMap.<HttpRequest, HttpResponse> builder()
                       .put(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess)
                       .put(listAZs, listAZsResponseForUnassigned)
                       .put(extensionsOfNovaRequest, extensionsOfNovaResponse)
                       .put(createFloatingIP, createFloatingIPResponse)
                       .put(addFloatingIPRequest, addFloatingIPResponse)
                       .put(list, listResponseForUnassigned).build())
              .getContext().utils().injector()
              .getInstance(AllocateAndAddFloatingIpToNode.class);

      AtomicReference<NodeMetadata> nodeRef = Atomics.newReference(node);
      AtomicReference<NovaTemplateOptions> optionsRef = Atomics.newReference(options);
      AtomicReference<NodeAndNovaTemplateOptions> nodeNovaRef = NodeAndNovaTemplateOptions.newAtomicReference(nodeRef, optionsRef);

      fn.apply(nodeNovaRef);
      NodeMetadata node1 = nodeRef.get();
      assertNotNull(node1);
      assertNotNull(optionsRef.get());
      assertEquals(node1.getPublicAddresses(), ImmutableSet.of("10.0.0.5"));
   }
}
