//
// 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.apache.cloudstack.diagnostics;

import com.cloud.agent.AgentManager;
import com.cloud.agent.api.routing.NetworkElementCommand;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.dao.VMInstanceDao;
import junit.framework.TestCase;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.diagnostics.RunDiagnosticsCmd;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.HashMap;
import java.util.Map;

@RunWith(MockitoJUnitRunner.class)
public class DiagnosticsServiceImplTest extends TestCase {

    @Mock
    private AgentManager agentManager;
    @Mock
    private VMInstanceDao instanceDao;
    @Mock
    private RunDiagnosticsCmd runDiagnosticsCmd;
    @Mock
    private DiagnosticsCommand command;
    @Mock
    private VMInstanceVO vmInstanceVO;
    @Mock
    private VirtualMachineManager vmManager;
    @Mock
    private NetworkOrchestrationService networkManager;

    @InjectMocks
    private DiagnosticsServiceImpl serviceImpl = new DiagnosticsServiceImpl();

    @Before
    public void setUp() throws Exception {
        Mockito.when(runDiagnosticsCmd.getId()).thenReturn(1L);
        Mockito.when(runDiagnosticsCmd.getType()).thenReturn(DiagnosticsType.PING);
        Mockito.when(instanceDao.findByIdTypes(Mockito.anyLong(), Mockito.any(VirtualMachine.Type.class),
                Mockito.any(VirtualMachine.Type.class), Mockito.any(VirtualMachine.Type.class))).thenReturn(vmInstanceVO);
    }

    @After
    public void tearDown() throws Exception {
        Mockito.reset(runDiagnosticsCmd);
        Mockito.reset(agentManager);
        Mockito.reset(instanceDao);
        Mockito.reset(vmInstanceVO);
        Mockito.reset(command);
    }

    @Test
    public void testRunDiagnosticsCommandTrue() throws Exception {
        Mockito.when(runDiagnosticsCmd.getAddress()).thenReturn("8.8.8.8");
        Map<String, String> accessDetailsMap = new HashMap<>();
        accessDetailsMap.put(NetworkElementCommand.ROUTER_IP, "169.20.175.10");
        Mockito.when(networkManager.getSystemVMAccessDetails(Mockito.any(VMInstanceVO.class))).thenReturn(accessDetailsMap);
        final String details = "PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n" +
                "64 bytes from 8.8.8.8: icmp_seq=1 ttl=125 time=7.88 ms\n" +
                "64 bytes from 8.8.8.8: icmp_seq=2 ttl=125 time=251 ms\n" +
                "64 bytes from 8.8.8.8: icmp_seq=3 ttl=125 time=64.9 ms\n" +
                "64 bytes from 8.8.8.8: icmp_seq=4 ttl=125 time=50.7 ms\n" +
                "64 bytes from 8.8.8.8: icmp_seq=5 ttl=125 time=67.9 ms\n" +
                "\n" +
                "--- 8.8.8.8 ping statistics ---\n" +
                "5 packets transmitted, 5 received, 0% packet loss, time 4003ms\n" +
                "rtt min/avg/max/mdev = 7.881/88.587/251.410/84.191 ms&&\n" +
                "&&\n" +
                "0\n";

        Mockito.when(agentManager.easySend(Mockito.anyLong(), Mockito.any(DiagnosticsCommand.class))).thenReturn(new DiagnosticsAnswer(command, true, details));

        Map<String, String> detailsMap = serviceImpl.runDiagnosticsCommand(runDiagnosticsCmd);

        String stdout = "PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.\n" +
                "64 bytes from 8.8.8.8: icmp_seq=1 ttl=125 time=7.88 ms\n" +
                "64 bytes from 8.8.8.8: icmp_seq=2 ttl=125 time=251 ms\n" +
                "64 bytes from 8.8.8.8: icmp_seq=3 ttl=125 time=64.9 ms\n" +
                "64 bytes from 8.8.8.8: icmp_seq=4 ttl=125 time=50.7 ms\n" +
                "64 bytes from 8.8.8.8: icmp_seq=5 ttl=125 time=67.9 ms\n" +
                "\n" +
                "--- 8.8.8.8 ping statistics ---\n" +
                "5 packets transmitted, 5 received, 0% packet loss, time 4003ms\n" +
                "rtt min/avg/max/mdev = 7.881/88.587/251.410/84.191 ms";

        assertEquals(3, detailsMap.size());
        assertEquals("Mismatch between actual and expected STDERR", "", detailsMap.get(ApiConstants.STDERR));
        assertEquals("Mismatch between actual and expected EXITCODE", "0", detailsMap.get(ApiConstants.EXITCODE));
        assertEquals("Mismatch between actual and expected STDOUT", stdout, detailsMap.get(ApiConstants.STDOUT));
    }

    @Test
    public void testRunDiagnosticsCommandFalse() throws Exception {
        Mockito.when(runDiagnosticsCmd.getAddress()).thenReturn("192.0.2.2");

        Map<String, String> accessDetailsMap = new HashMap<>();
        accessDetailsMap.put(NetworkElementCommand.ROUTER_IP, "169.20.175.10");
        Mockito.when(networkManager.getSystemVMAccessDetails(Mockito.any(VMInstanceVO.class))).thenReturn(accessDetailsMap);

        String details = "PING 192.0.2.2 (192.0.2.2): 56 data bytes\n" +
                "76 bytes from 213.130.48.253: Destination Net Unreachable\n" +
                "--- 192.0.2.2 ping statistics ---\n" +
                "4 packets transmitted, 0 packets received, 100% packet loss&&\n" +
                "&&\n" +
                "1\n";
        String stdout = "PING 192.0.2.2 (192.0.2.2): 56 data bytes\n" +
                "76 bytes from 213.130.48.253: Destination Net Unreachable\n" +
                "--- 192.0.2.2 ping statistics ---\n" +
                "4 packets transmitted, 0 packets received, 100% packet loss";
        Mockito.when(agentManager.easySend(Mockito.anyLong(), Mockito.any(DiagnosticsCommand.class))).thenReturn(new DiagnosticsAnswer(command, true, details));

        Map<String, String> detailsMap = serviceImpl.runDiagnosticsCommand(runDiagnosticsCmd);

        assertEquals(3, detailsMap.size());
        assertEquals("Mismatch between actual and expected STDERR", "", detailsMap.get(ApiConstants.STDERR));
        assertTrue("Mismatch between actual and expected EXITCODE", !detailsMap.get(ApiConstants.EXITCODE).equalsIgnoreCase("0"));
        assertEquals("Mismatch between actual and expected STDOUT", stdout, detailsMap.get(ApiConstants.STDOUT));
    }

    @Test(expected = InvalidParameterValueException.class)
    public void testRunDiagnosticsThrowsInvalidParamException() throws Exception {
        Mockito.when(runDiagnosticsCmd.getAddress()).thenReturn("");
        Mockito.when(instanceDao.findByIdTypes(Mockito.anyLong(), Mockito.any(VirtualMachine.Type.class),
                Mockito.any(VirtualMachine.Type.class), Mockito.any(VirtualMachine.Type.class))).thenReturn(null);

        serviceImpl.runDiagnosticsCommand(runDiagnosticsCmd);
    }

    @Test(expected = CloudRuntimeException.class)
    public void testVMControlIPisNull() throws Exception {
        Mockito.when(runDiagnosticsCmd.getAddress()).thenReturn("0.42.42.42");

        Map<String, String> accessDetailsMap = new HashMap<>();
        accessDetailsMap.put(NetworkElementCommand.ROUTER_IP, null);
        Mockito.when(networkManager.getSystemVMAccessDetails(Mockito.any(VMInstanceVO.class))).thenReturn(accessDetailsMap);

        serviceImpl.runDiagnosticsCommand(runDiagnosticsCmd);
    }

    @Test
    public void testInvalidCharsInParams() throws Exception {
        assertFalse(serviceImpl.hasValidChars("'\\''"));
        assertFalse(serviceImpl.hasValidChars("-I eth0 &"));
        assertFalse(serviceImpl.hasValidChars("-I eth0 ;"));
        assertFalse(serviceImpl.hasValidChars(" &2 > "));
        assertFalse(serviceImpl.hasValidChars(" &2 >> "));
        assertFalse(serviceImpl.hasValidChars(" | "));
        assertFalse(serviceImpl.hasValidChars("|"));
        assertFalse(serviceImpl.hasValidChars(","));
    }

    @Test
    public void testValidCharsInParams() throws Exception {
        assertTrue(serviceImpl.hasValidChars(""));
        assertTrue(serviceImpl.hasValidChars("."));
        assertTrue(serviceImpl.hasValidChars(" "));
        assertTrue(serviceImpl.hasValidChars("-I eth0 www.google.com"));
        assertTrue(serviceImpl.hasValidChars(" "));
        assertTrue(serviceImpl.hasValidChars(" -I cloudbr0 --sport "));
        assertTrue(serviceImpl.hasValidChars(" --back -m20 "));
        assertTrue(serviceImpl.hasValidChars("-c 5 -4"));
        assertTrue(serviceImpl.hasValidChars("-c 5 -4 -AbDfhqUV"));
    }

}
