/* * 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.usergrid.corepersistence.migration; import com.google.common.base.Optional; import org.apache.usergrid.NewOrgAppAdminRule; import org.apache.usergrid.ServiceITSetup; import org.apache.usergrid.ServiceITSetupImpl; import org.apache.usergrid.cassandra.ClearShiroSubject; import org.apache.usergrid.corepersistence.util.CpNamingUtils; import org.apache.usergrid.management.AppInfoMigrationPlugin; import org.apache.usergrid.management.ManagementService; import org.apache.usergrid.management.OrganizationOwnerInfo; import org.apache.usergrid.persistence.*; import org.apache.usergrid.persistence.cassandra.CassandraService; import org.apache.usergrid.persistence.collection.EntityCollectionManagerFactory; import org.apache.usergrid.persistence.core.migration.data.MigrationInfoSerialization; import org.apache.usergrid.persistence.core.migration.data.ProgressObserver; import org.apache.usergrid.persistence.entities.Application; import org.apache.usergrid.persistence.graph.GraphManagerFactory; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import java.util.*; import static org.apache.usergrid.TestHelper.*; import static org.junit.Assert.*; /** * Really a Core module test but it needs to be here so that it can use * ManagementService's application and organization logic. */ public class AppInfoMigrationPluginTest { private static final Logger logger = LoggerFactory.getLogger(AppInfoMigrationPluginTest.class); @Rule public ClearShiroSubject clearShiroSubject = new ClearShiroSubject(); @ClassRule public static ServiceITSetup setup = new ServiceITSetupImpl(); @Rule public NewOrgAppAdminRule orgAppRule = new NewOrgAppAdminRule( setup ); @Test public void testRun() throws Exception { MigrationInfoSerialization serialization = Mockito.mock(MigrationInfoSerialization.class); Mockito.when(serialization.getVersion(Mockito.any())).thenReturn(0); EntityCollectionManagerFactory ecmf = setup.getInjector().getInstance(EntityCollectionManagerFactory.class); GraphManagerFactory gmf = setup.getInjector().getInstance(GraphManagerFactory.class); ManagementService managementService = setup.getMgmtSvc(); AppInfoMigrationPlugin appInfoMigrationPlugin = new AppInfoMigrationPlugin(setup.getEmf(),serialization,ecmf,gmf,managementService); // create 10 applications, each with 10 entities logger.debug("\n\nCreate 10 apps each with 10 entities"); final String orgName = uniqueOrg(); OrganizationOwnerInfo organization = orgAppRule.createOwnerAndOrganization( orgName, uniqueUsername(), uniqueEmail(),"Ed Anuff", "test" ); List<UUID> appIds = new ArrayList<>(); /*** * Create all 10 apps in the new format */ for ( int i=0; i<10; i++ ) { UUID appId = setup.getMgmtSvc().createApplication( organization.getOrganization().getUuid(), "application" + i ).getId(); appIds.add( appId ); EntityManager em = setup.getEmf().getEntityManager( appId ); for ( int j=0; j<10; j++ ) { final String entityName = "thing" + j; em.create("thing", new HashMap<String, Object>() {{ put("name", entityName ); }}); } } UUID mgmtAppId = setup.getEmf().getManagementAppId(); setup.refreshIndex(mgmtAppId); EntityManager rootEm = setup.getEmf().getEntityManager( mgmtAppId ); checkApplicationsOk( orgName ); // create corresponding 10 appinfo entities in Management app // and delete the application_info entities from the Management app logger.debug("\n\nCreate old-style appinfo entities to be migrated\n"); List<Entity> deletedApps = new ArrayList<>(); setup.getEmf().initializeApplicationV2( CassandraService.DEFAULT_ORGANIZATION, AppInfoMigrationPlugin.SYSTEM_APP_ID, "systemapp", null, false); EntityManager systemAppEm = setup.getEmf().getEntityManager( AppInfoMigrationPlugin.SYSTEM_APP_ID ); int count = 0; /** * Now to ensure the process is idempotent, we "move" half of our app infos into the old system and remove them. * Once they're migrated, we should get all 10 */ for ( UUID appId : appIds ) { final Entity applicationInfo = getApplicationInfo( appId ); final String appName = applicationInfo.getName(); final String finalOrgId = organization.getOrganization().getUuid().toString(); final String finalAppId = applicationInfo.asId().getUuid().toString(); systemAppEm.create("appinfo", new HashMap<String, Object>() {{ put("name", appName ); put("organizationUuid", finalOrgId ); put("applicationUuid", finalAppId ); }}); // delete some but not all of the application_info entities // so that we cover both create and update cases if ( count++ % 2 == 0 ) { rootEm.delete( applicationInfo ); deletedApps.add( applicationInfo ); } } setup.refreshIndex(mgmtAppId); setup.getEmf().flushEntityManagerCaches(); Thread.sleep(1000); // test that applications are now broken checkApplicationsBroken(deletedApps); // run the migration, which should restore the application_info entities logger.debug("\n\nRun the migration\n"); ProgressObserver po = Mockito.mock(ProgressObserver.class); appInfoMigrationPlugin.run(po); logger.debug("\n\nVerify migration results\n"); // test that expected calls were made the to progress observer (use mock library) Mockito.verify( po, Mockito.times(10) ).update( Mockito.anyInt(), Mockito.anyString() ); setup.refreshIndex(mgmtAppId); final Results appInfoResults = rootEm.searchCollection( new SimpleEntityRef("application", mgmtAppId), "appinfos", Query.fromQL("select *")); assertEquals( 0, appInfoResults.size() ); final Results applicationInfoResults = rootEm.searchCollection( new SimpleEntityRef("application", mgmtAppId), CpNamingUtils.APPLICATION_INFOS, Query.fromQL("select *")); int appCount = Observable.from( applicationInfoResults.getEntities() ).filter( entity -> !entity.getName().startsWith( "org." ) ).doOnNext( entity -> logger.info("counting entity {}", entity) ).count().toBlocking().last(); assertEquals( appIds.size() ,appCount ); // test that 10 applications are no longer broken checkApplicationsOk( orgName ); } private void checkApplicationsBroken( List<Entity> deletedApps ) throws Exception { if (logger.isDebugEnabled()) { logger.debug("\n\nChecking applications broken\n"); } for ( Entity applicationInfo : deletedApps ) { String appName = applicationInfo.getName(); boolean isPresent = setup.getEmf().lookupApplication( appName ) != null; // missing application_info does not completely break applications, but we... assertFalse("Should not be able to lookup deleted application by name" + appName, isPresent); } } private void checkApplicationsOk( String orgName) throws Exception { if (logger.isDebugEnabled()) { logger.debug("\n\nChecking applications OK\n"); } for (int i=0; i<10; i++) { String appName = orgName + "/application" + i; UUID uuid = setup.getEmf().lookupApplication(appName); assertTrue ("Should be able to get application", uuid != null ); EntityManager em = setup.getEmf().getEntityManager( uuid ); Application app = em.getApplication(); assertEquals( appName, app.getName() ); Results results = em.searchCollection( em.getApplicationRef(), "things", Query.fromQL("select *")); assertEquals( "Should have 10 entities", 10, results.size() ); } } private Entity getApplicationInfo( UUID appId ) throws Exception { Map<String, UUID> apps = setup.getEmf().getApplications(); if(apps.containsValue(appId)){ return setup.getEmf().getManagementEntityManager().get(appId); }else{ fail("no app " + appId); return null; } } }