/*
* 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;
}
}
}