content: // 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.cloud.upgrade;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertNotNull;
  import static org.junit.Assert.assertNull;
  import static org.junit.Assert.assertThrows;
  import static org.junit.Assert.assertTrue;
  import static org.junit.Assert.fail;
  import static org.mockito.ArgumentMatchers.any;
  import static org.mockito.ArgumentMatchers.anyString;
  import static org.mockito.ArgumentMatchers.eq;
  import static org.mockito.Mockito.doNothing;
  import static org.mockito.Mockito.doReturn;
  import static org.mockito.Mockito.times;
  import static org.mockito.Mockito.verify;
  import static org.mockito.Mockito.when;
  
  import java.io.File;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.util.ArrayList;
  import java.util.List;
  
  import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
  import org.apache.cloudstack.utils.security.DigestHelper;
  import org.apache.commons.lang3.StringUtils;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  import org.mockito.InjectMocks;
  import org.mockito.Mock;
  import org.mockito.MockedStatic;
  import org.mockito.Mockito;
  import org.mockito.Spy;
  import org.mockito.junit.MockitoJUnitRunner;
  
  import com.cloud.cpu.CPU;
  import com.cloud.dc.dao.ClusterDao;
  import com.cloud.hypervisor.Hypervisor;
  import com.cloud.storage.VMTemplateVO;
  import com.cloud.storage.dao.VMTemplateDao;
  import com.cloud.utils.HttpUtils;
  import com.cloud.utils.Pair;
  import com.cloud.utils.UriUtils;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.script.Script;
  
  @RunWith(MockitoJUnitRunner.class)
  public class SystemVmTemplateRegistrationTest {
  
      @Mock
      ClusterDao clusterDao;
  
      @Mock
      VMTemplateDao vmTemplateDao;
  
      @Spy
      @InjectMocks
      SystemVmTemplateRegistration systemVmTemplateRegistration = new SystemVmTemplateRegistration();
  
      private void setupMetadataFile(MockedStatic<SystemVmTemplateRegistration> mockedStatic, String content) {
          try {
              String location = "metadata.ini";
              if (StringUtils.isNotBlank(content)) {
                  File tempFile = File.createTempFile("metadata", ".ini");
                  location = tempFile.getAbsolutePath();
                  Files.write(Paths.get(location), content.getBytes());
                  tempFile.deleteOnExit();
              }
              mockedStatic.when(SystemVmTemplateRegistration::getMetadataFilePath).thenReturn(location);
          } catch (Exception e) {
              fail(e.getMessage());
          }
      }
  
      @Test
      public void test_parseMetadataFile_noFile() {
          try (MockedStatic<SystemVmTemplateRegistration> mockedStatic =
                       Mockito.mockStatic(SystemVmTemplateRegistration.class, Mockito.CALLS_REAL_METHODS)) {
              setupMetadataFile(mockedStatic, null);
              CloudRuntimeException exception = assertThrows(CloudRuntimeException.class,
                      SystemVmTemplateRegistration::parseMetadataFile);
              assertTrue(exception.getMessage().contains("Failed to parse systemVM template metadata file"));
          }
      }
  
      @Test
      public void test_parseMetadataFile_invalidContent() {
          try (MockedStatic<SystemVmTemplateRegistration> mockedStatic =
                       Mockito.mockStatic(SystemVmTemplateRegistration.class, Mockito.CALLS_REAL_METHODS)) {
              setupMetadataFile(mockedStatic, "abc");
              CloudRuntimeException exception = assertThrows(CloudRuntimeException.class,
                      SystemVmTemplateRegistration::parseMetadataFile);
              assertTrue(exception.getMessage().contains("Failed to parse systemVM template metadata file"));
          }
      }
  
      @Test
      public void test_parseMetadataFile_success() {
          String metadataFileContent = "[default]\n" +
                  "version = x.y.z.0\n" +
                  "\n" +
                  "[kvm-x86_64]\n" +
                  "templatename = systemvm-kvm-x.y.z\n" +
                  "checksum = abc1\n" +
                  "downloadurl = https://download.cloudstack.org/systemvm/x.y/systemvmtemplate-x.y.z-kvm.qcow2.bz2\n" +
                  "filename = systemvmtemplate-x.y.z-kvm.qcow2.bz2\n" +
                  "\n" +
                  "[kvm-aarch64]\n" +
                  "templatename = systemvm-kvm-x.y.z\n" +
                  "checksum = abc2\n" +
                  "downloadurl = https://download.cloudstack.org/systemvm/x.y/systemvmtemplate-x.y.z-kvm.qcow2.bz2\n" +
                  "filename = systemvmtemplate-x.y.z-kvm.qcow2.bz2\n" +
                  "\n" +
                  "[vmware]\n" +
                  "templatename = systemvm-vmware-x.y.z\n" +
                  "checksum = abc3\n" +
                  "downloadurl = https://download.cloudstack.org/systemvm/x.y/systemvmtemplate-x.y.z-vmware.ova\n" +
                  "filename = systemvmtemplate-x.y.z-vmware.ova\n";
          try (MockedStatic<SystemVmTemplateRegistration> mockedStatic =
                       Mockito.mockStatic(SystemVmTemplateRegistration.class, Mockito.CALLS_REAL_METHODS)) {
              setupMetadataFile(mockedStatic, metadataFileContent);
              String version = SystemVmTemplateRegistration.parseMetadataFile();
              assertEquals("x.y.z.0", version);
          }
          assertNull(SystemVmTemplateRegistration.NewTemplateMap.get("xenserver"));
          SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails =
                  SystemVmTemplateRegistration.NewTemplateMap.get("kvm-x86_64");
          assertNotNull(templateDetails);
          assertEquals(CPU.CPUArch.amd64, templateDetails.getArch());
          assertEquals(Hypervisor.HypervisorType.KVM, templateDetails.getHypervisorType());
          templateDetails =
                  SystemVmTemplateRegistration.NewTemplateMap.get("kvm-aarch64");
          assertNotNull(templateDetails);
          assertEquals(CPU.CPUArch.arm64, templateDetails.getArch());
          assertEquals(Hypervisor.HypervisorType.KVM, templateDetails.getHypervisorType());
          templateDetails =
                  SystemVmTemplateRegistration.NewTemplateMap.get("vmware");
          assertNotNull(templateDetails);
          assertNull(templateDetails.getArch());
          assertEquals(Hypervisor.HypervisorType.VMware, templateDetails.getHypervisorType());
      }
  
      @Test
      public void testMountStore_nullStoreUrl() throws Exception {
          try (MockedStatic<Script> scriptMock = Mockito.mockStatic(Script.class)) {
              SystemVmTemplateRegistration.mountStore(null, "/mnt/nfs", "nfs3");
              scriptMock.verifyNoInteractions();
          }
      }
  
      @Test
      public void testMountStore_validStoreUrl() throws Exception {
          String storeUrl = "nfs://192.168.1.100/export";
          String path = "/mnt/nfs";
          String nfsVersion = "nfs3";
          String expectedMountCommand = "expectedMountCommand";
          try (MockedStatic<UriUtils> uriUtilsMock = Mockito.mockStatic(UriUtils.class);
               MockedStatic<SystemVmTemplateRegistration> sysVmMock =
                       Mockito.mockStatic(SystemVmTemplateRegistration.class, Mockito.CALLS_REAL_METHODS);
               MockedStatic<Script> scriptMock = Mockito.mockStatic(Script.class)) {
              uriUtilsMock.when(() -> UriUtils.encodeURIComponent(storeUrl)).thenReturn(storeUrl);
              sysVmMock.when(() -> SystemVmTemplateRegistration.getMountCommand(
                      eq(nfsVersion),
                      eq("192.168.1.100:/export"),
                      eq(path)
              )).thenReturn(expectedMountCommand);
              SystemVmTemplateRegistration.mountStore(storeUrl, path, nfsVersion);
              scriptMock.verify(() -> Script.runSimpleBashScript(expectedMountCommand), times(1));
          }
      }
  
      @Test
      public void testValidateTemplateFile_fileNotFound() {
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  new SystemVmTemplateRegistration.MetadataTemplateDetails(Hypervisor.HypervisorType.KVM,
                          "name", "file", "url", "checksum", CPU.CPUArch.amd64, "guestos");
          SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey(
                  details.getHypervisorType(), details.getArch()), details);
          doReturn(null).when(systemVmTemplateRegistration).getTemplateFile(details);
          try {
              systemVmTemplateRegistration.validateTemplateFileForHypervisorAndArch(details.getHypervisorType(),
                      details.getArch());
              fail("Expected CloudRuntimeException due to missing template file");
          } catch (CloudRuntimeException e) {
              assertEquals("Failed to find local template file", e.getMessage());
          }
      }
  
      @Test
      public void testValidateTemplateFile_checksumMismatch() {
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  new SystemVmTemplateRegistration.MetadataTemplateDetails(Hypervisor.HypervisorType.KVM,
                          "name", "file", "url", "checksum", CPU.CPUArch.amd64, "guestos");
          File dummyFile = new File("dummy.txt");
          SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey(
                  details.getHypervisorType(), details.getArch()), details);
          doReturn(dummyFile).when(systemVmTemplateRegistration).getTemplateFile(details);
          doReturn(true).when(systemVmTemplateRegistration).isTemplateFileChecksumDifferent(details, dummyFile);
          try {
              systemVmTemplateRegistration.validateTemplateFileForHypervisorAndArch(details.getHypervisorType(),
                      details.getArch());
              fail("Expected CloudRuntimeException due to checksum failure");
          } catch (CloudRuntimeException e) {
              assertEquals("Checksum failed for local template file", e.getMessage());
          }
      }
  
      @Test
      public void testValidateTemplateFile_success() {
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  new SystemVmTemplateRegistration.MetadataTemplateDetails(Hypervisor.HypervisorType.KVM,
                          "name", "file", "url", "checksum", CPU.CPUArch.amd64, "guestos");
          File dummyFile = new File("dummy.txt");
          SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey(
                  details.getHypervisorType(), details.getArch()), details);
          doReturn(dummyFile).when(systemVmTemplateRegistration).getTemplateFile(details);
          doReturn(false).when(systemVmTemplateRegistration).isTemplateFileChecksumDifferent(details, dummyFile);
          systemVmTemplateRegistration.validateTemplateFileForHypervisorAndArch(details.getHypervisorType(),
                  details.getArch());
      }
  
      @Test
      public void testValidateAndRegisterTemplate() {
          Hypervisor.HypervisorType hypervisor = Hypervisor.HypervisorType.KVM;
          String name = "TestTemplate";
          Long storeId = 123L;
          VMTemplateVO templateVO = new VMTemplateVO();
          templateVO.setArch(CPU.CPUArch.x86);
          TemplateDataStoreVO templateDataStoreVO = new TemplateDataStoreVO();
          String filePath = "/dummy/path";
          doNothing().when(systemVmTemplateRegistration).validateTemplateFileForHypervisorAndArch(hypervisor, templateVO.getArch());
          doNothing().when(systemVmTemplateRegistration).registerTemplate(hypervisor, name, storeId, templateVO, templateDataStoreVO, filePath);
          systemVmTemplateRegistration.validateAndRegisterTemplate(hypervisor, name, storeId, templateVO, templateDataStoreVO, filePath);
          verify(systemVmTemplateRegistration).validateTemplateFileForHypervisorAndArch(eq(hypervisor), eq(templateVO.getArch()));
          verify(systemVmTemplateRegistration).registerTemplate(eq(hypervisor), eq(name), eq(storeId), eq(templateVO), eq(templateDataStoreVO), eq(filePath));
      }
  
      @Test
      public void testValidateAndRegisterTemplateForNonExistingEntries() {
          Hypervisor.HypervisorType hypervisor = Hypervisor.HypervisorType.KVM;
          CPU.CPUArch arch = CPU.CPUArch.amd64;
          String name = "TestTemplateNonExisting";
          Pair<String, Long> storeUrlAndId = new Pair<>("nfs://dummy", 456L);
          String filePath = "/dummy/path/nonexisting";
          doNothing().when(systemVmTemplateRegistration).validateTemplateFileForHypervisorAndArch(hypervisor, arch);
          doNothing().when(systemVmTemplateRegistration).registerTemplateForNonExistingEntries(hypervisor, arch, name, storeUrlAndId, filePath);
          systemVmTemplateRegistration.validateAndRegisterTemplateForNonExistingEntries(hypervisor, arch, name, storeUrlAndId, filePath);
          verify(systemVmTemplateRegistration).validateTemplateFileForHypervisorAndArch(eq(hypervisor), eq(arch));
          verify(systemVmTemplateRegistration).registerTemplateForNonExistingEntries(eq(hypervisor), eq(arch), eq(name), eq(storeUrlAndId), eq(filePath));
      }
  
      @Test
      public void testGetTemplateFile_fileExists() throws Exception {
          File tempFile = File.createTempFile("template", ".qcow2");
          tempFile.deleteOnExit();
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class);
          when(details.getDefaultFilePath()).thenReturn(tempFile.getAbsolutePath());
          File result = systemVmTemplateRegistration.getTemplateFile(details);
          assertNotNull(result);
          assertEquals(tempFile.getAbsolutePath(), result.getAbsolutePath());
      }
  
      @Test
      public void testGetTemplateFile_fileDoesNotExist_downloadFails() {
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  new SystemVmTemplateRegistration.MetadataTemplateDetails(Hypervisor.HypervisorType.KVM,
                          "name", "nonexistent.qcow2", "http://example.com/file.qcow2",
                          "", CPU.CPUArch.arm64, "guestos");
          try (MockedStatic<Files> filesMock = Mockito.mockStatic(Files.class);
               MockedStatic<HttpUtils> httpMock = Mockito.mockStatic(HttpUtils.class)) {
              filesMock.when(() -> Files.isWritable(any(Path.class))).thenReturn(true);
              httpMock.when(() -> HttpUtils.downloadFileWithProgress(eq(details.getUrl()), anyString(), any()))
                      .thenReturn(false);
              File result = systemVmTemplateRegistration.getTemplateFile(details);
              assertNull(result);
          }
      }
  
      @Test
      public void testGetTemplateFile_fileDoesNotExist_downloadSucceeds() {
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  new SystemVmTemplateRegistration.MetadataTemplateDetails(Hypervisor.HypervisorType.KVM,
                          "name", "file.qcow2", "http://example.com/file.qcow2",
                          "", CPU.CPUArch.arm64, "guestos");
          try (MockedStatic<Files> filesMock = Mockito.mockStatic(Files.class);
               MockedStatic<HttpUtils> httpMock = Mockito.mockStatic(HttpUtils.class)) {
              filesMock.when(() -> Files.isWritable(any(Path.class))).thenReturn(false);
              File expectedFile = new File(systemVmTemplateRegistration.getTempDownloadDir(), details.getFilename());
              httpMock.when(() -> HttpUtils.downloadFileWithProgress(eq(details.getUrl()), eq(expectedFile.getAbsolutePath()), any()))
                      .thenReturn(true);
              File result = systemVmTemplateRegistration.getTemplateFile(details);
              assertNotNull(result);
              assertEquals(expectedFile.getAbsolutePath(), result.getAbsolutePath());
              assertEquals(expectedFile.getAbsolutePath(), details.getDownloadedFilePath());
          }
      }
  
      @Test
      public void testIsTemplateFileChecksumDifferent_noMismatch() {
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class);
          when(details.getChecksum()).thenReturn("dummyChecksum");
          File file = new File("dummy.txt");
          try (MockedStatic<DigestHelper> digestMock = Mockito.mockStatic(DigestHelper.class)) {
              digestMock.when(() -> DigestHelper.calculateChecksum(file)).thenReturn("dummyChecksum");
              boolean result = systemVmTemplateRegistration.isTemplateFileChecksumDifferent(details, file);
              assertFalse(result);
          }
      }
  
      @Test
      public void testIsTemplateFileChecksumDifferent_mismatch() {
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class);
          when(details.getChecksum()).thenReturn("expectedChecksum");
          File file = new File("dummy.txt");
          try (MockedStatic<DigestHelper> digestMock = Mockito.mockStatic(DigestHelper.class)) {
              digestMock.when(() -> DigestHelper.calculateChecksum(file)).thenReturn("actualChecksum");
              boolean result = systemVmTemplateRegistration.isTemplateFileChecksumDifferent(details, file);
              assertTrue(result);
          }
      }
  
      @Test(expected = CloudRuntimeException.class)
      public void testValidateTemplates_metadataTemplateFailure() {
          List<Pair<Hypervisor.HypervisorType, CPU.CPUArch>> list = new ArrayList<>();
          list.add(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64));
          systemVmTemplateRegistration.validateTemplates(list);
      }
  
      @Test(expected = CloudRuntimeException.class)
      public void testValidateTemplates_fileFailure() {
          List<Pair<Hypervisor.HypervisorType, CPU.CPUArch>> list = new ArrayList<>();
          list.add(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64));
  
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class);
          SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey(
                  Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64), details);
          File mockFile = Mockito.mock(File.class);
          doReturn(mockFile).when(systemVmTemplateRegistration).getTemplateFile(details);
          doReturn(true).when(systemVmTemplateRegistration).isTemplateFileChecksumDifferent(details, mockFile);
          systemVmTemplateRegistration.validateTemplates(list);
      }
  
      public void testValidateTemplates_downloadableFileNotFound() {
          CPU.CPUArch arch = SystemVmTemplateRegistration.DOWNLOADABLE_TEMPLATE_ARCH_TYPES.get(0);
          List<Pair<Hypervisor.HypervisorType, CPU.CPUArch>> list = new ArrayList<>();
          list.add(new Pair<>(Hypervisor.HypervisorType.KVM, arch));
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class);
          SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey(
                  Hypervisor.HypervisorType.KVM, arch), details);
          doReturn(null).when(systemVmTemplateRegistration).getTemplateFile(details);
          systemVmTemplateRegistration.validateTemplates(list);
      }
  
      @Test
      public void testValidateTemplates_success() {
          List<Pair<Hypervisor.HypervisorType, CPU.CPUArch>> list = new ArrayList<>();
          list.add(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64));
  
          SystemVmTemplateRegistration.MetadataTemplateDetails details =
                  Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class);
          SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey(
                  Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64), details);
          File mockFile = Mockito.mock(File.class);
          doReturn(mockFile).when(systemVmTemplateRegistration).getTemplateFile(details);
          doReturn(false).when(systemVmTemplateRegistration).isTemplateFileChecksumDifferent(details, mockFile);
          systemVmTemplateRegistration.validateTemplates(list);
      }
  
      @Test
      public void testRegisterTemplatesForZone() {
          long zoneId = 1L;
          String filePath = "dummyFilePath";
          String nfsVersion = "nfs3";
          Pair<String, Long> storeUrlAndId = new Pair<>("nfs://dummy", 100L);
          doReturn(storeUrlAndId).when(systemVmTemplateRegistration).getNfsStoreInZone(zoneId);
          doReturn(nfsVersion).when(systemVmTemplateRegistration).getNfsVersion(storeUrlAndId.second());
          try (MockedStatic<SystemVmTemplateRegistration> mockedStatic = Mockito.mockStatic(
                  SystemVmTemplateRegistration.class)) {
              List<Pair<Hypervisor.HypervisorType, CPU.CPUArch>> hypervisorArchList = new ArrayList<>();
              Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM;
              CPU.CPUArch arch = CPU.CPUArch.getDefault();
              hypervisorArchList.add(new Pair<>(hypervisorType, arch));
              doReturn(hypervisorArchList).when(clusterDao).listDistinctHypervisorsAndArchExcludingExternalType(zoneId);
              SystemVmTemplateRegistration.MetadataTemplateDetails details =
                      Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class);
              String name = "existing";
              Mockito.when(details.getArch()).thenReturn(CPU.CPUArch.getDefault());
              Mockito.when(details.getName()).thenReturn(name);
              mockedStatic.when(() -> SystemVmTemplateRegistration.getMetadataTemplateDetails(Mockito.any(),
                      Mockito.any())).thenReturn(details);
              when(systemVmTemplateRegistration.getRegisteredTemplate(name, arch))
                      .thenReturn(null);
              doNothing().when(systemVmTemplateRegistration).registerTemplateForNonExistingEntries(
                      hypervisorType, arch,
                      name, storeUrlAndId, filePath);
              systemVmTemplateRegistration.registerTemplatesForZone(zoneId, filePath);
              mockedStatic.verify(() -> SystemVmTemplateRegistration.mountStore(storeUrlAndId.first(), filePath,
                      nfsVersion));
              verify(systemVmTemplateRegistration).registerTemplateForNonExistingEntries(hypervisorType,
                      arch, name, storeUrlAndId, filePath);
          }
      }
  }
  