/** * This work was created by participants in the DataONE project, and is * jointly copyrighted by participating institutions in DataONE. For * more information on DataONE, see our web site at http://dataone.org. * * Copyright ${year} * * Licensed 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. * * $Id$ */ package org.dataone.integration.it.functional; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.dataone.client.v1.CNode; import org.dataone.client.D1Client; import org.dataone.client.D1Node; import org.dataone.client.v1.types.D1TypeBuilder; import org.dataone.client.v1.MNode; import org.dataone.integration.APITestUtils; import org.dataone.integration.ContextAwareTestCaseDataone; import org.dataone.service.exceptions.BaseException; import org.dataone.service.exceptions.InvalidToken; import org.dataone.service.exceptions.NotAuthorized; import org.dataone.service.exceptions.NotFound; import org.dataone.service.exceptions.NotImplemented; import org.dataone.service.exceptions.ServiceFailure; import org.dataone.service.types.v1.AccessPolicy; import org.dataone.service.types.v1.AccessRule; import org.dataone.service.types.v1.Identifier; import org.dataone.service.types.v1.Node; import org.dataone.service.types.v1.Permission; import org.dataone.service.types.v1.Replica; import org.dataone.service.types.v1.Subject; import org.dataone.service.types.v1.SystemMetadata; import org.dataone.service.types.v1.util.AccessUtil; import org.dataone.service.util.Constants; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; /** * Tests that changes made to system metadata of an object are propagated back * to the authoritative and replica member nodes * @author rnahf * */ public class SysmetaChangeFuncIT extends ContextAwareTestCaseDataone { String currentUrl; Map createdObjectMap; List syncedMNs; int maxWaitMinutes = 5; @Before public void createTestObjects() throws ServiceFailure, InvalidToken, NotAuthorized, NotImplemented { if (createdObjectMap == null) { setupClientSubject_NoCert(); Iterator it = getMemberNodeIterator(); createdObjectMap = new HashMap(); int mnCount = 0; CNode cn = D1Client.getCN(); // create or find the test objects on the MNs while (it.hasNext()) { currentUrl = it.next().getBaseURL(); MNode mn = D1Client.getMN(currentUrl); try { Identifier pid = procureTestObject(mn, APITestUtils.buildAccessRule(Constants.SUBJECT_PUBLIC, Permission.READ), APITestUtils.buildIdentifier( "FunctionalTest:smdChange:" + cn.lookupNodeId(currentUrl) ) ); createdObjectMap.put(mn,pid); log.info(String.format("creating test object '%s' on MN '%s' ",pid.getValue(),currentUrl)); mnCount++; } catch (BaseException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (TestIterationEndingException e) { e.printStackTrace(); } } // make sure all test objects are known to the cn. // wait until all are accounted for Date startWait = new Date(); int syncedCount = 0; syncedMNs = new ArrayList(); while (syncedCount < mnCount) { for (MNode mn : createdObjectMap.keySet()) { if (!syncedMNs.contains(mn)) { try { cn.get(null, createdObjectMap.get(mn)); // cn.describe(null, createdObjectMap.get(mn)); syncedMNs.add(mn); syncedCount++; } catch (NotFound e) { ; // not synced yet } } } Date now = new Date(); if (startWait.getTime() + 10 * 60 * 1000 < now.getTime()) { // exceeded reasonable time for sync // proceed with what we've got break; } } } } @Test public void testSetRightsHolderUseCase() throws ServiceFailure, InterruptedException { Subject testPerson = setupClientSubject("testPerson"); Subject testRH = setupClientSubject("testRightsHolder"); int totalReplicaCount = 0; // change the systemMetadata on the CN Map expectedChange = new HashMap(); Map cnSmd = new HashMap(); CNode cn = D1Client.getCN(); for (MNode mn : createdObjectMap.keySet()) { currentUrl = mn.getNodeBaseServiceUrl(); Identifier pid = createdObjectMap.get(mn); try { // gather the 'before' info SystemMetadata smdCN = cn.getSystemMetadata(null, pid); // change the sysmeta on the cn if (smdCN.getRightsHolder().equals(testPerson)) { setupClientSubject("testPerson"); cn.setRightsHolder(null, pid, testRH, smdCN.getSerialVersion().longValue()); expectedChange.put(mn, setupClientSubject("testRightsHolder").getValue()); } else { setupClientSubject("testRightsHolder"); cn.setRightsHolder(null, pid, testPerson, smdCN.getSerialVersion().longValue()); expectedChange.put(mn, setupClientSubject("testPerson").getValue()); } cnSmd.put(pid,smdCN); totalReplicaCount += smdCN.getReplicaList().size(); } catch (BaseException e) { e.printStackTrace(); handleFail(currentUrl,e.getClass().getSimpleName() + ": " + e.getDetail_code() + ": " + e.getDescription()); } } testReplicas(cnSmd,expectedChange,"rightsHolder", "the updated sysmeta for '%s' should have '%s' as rightsHolder."); // check for changes to be reflected in the sysmeta on the replica MNs // Map> testedReplicas = new HashMap>(); // // but don't wait forever... // int maxMinutes = 5; // long cutoffTime = new Date().getTime() + maxMinutes * 60 * 1000; // int tested = 0; // while (cutoffTime > new Date().getTime() && tested < totalReplicaCount ) { // Thread.sleep(10 * 1000); // // // iterate through the pids of those successfully altered on the CN // Iterator it = expectedChange.keySet().iterator(); // while (it.hasNext()) { // MNode mn = it.next(); // Identifier pid = createdObjectMap.get(mn); // log.info("checking replicas of " + pid.getValue()); // // check each object's replicas // // for (Replica replica : cnSmd.get(pid).getReplicaList()) { // if (testedReplicas.get(pid) == null) // testedReplicas.put(pid, new ArrayList()); // // if (!testedReplicas.get(pid).contains(replica)) { // try { // D1Node d1Node = D1Client.getMN(replica.getReplicaMemberNode()); // SystemMetadata smdMN = d1Node.getSystemMetadata(null, pid); // // if (smdMN.getSerialVersion().compareTo(cnSmd.get(pid).getSerialVersion()) > 0) { // BigInts use this idiom for comparison // // there's been a change // log.info("pid: " + pid.getValue() + ". mn: " + replica.getReplicaMemberNode().getValue() + // ". mnRH: " + smdMN.getRightsHolder().getValue()); // checkEquals(currentUrl, String.format("the updated sysmeta for '%s' should have '%s' as rightsHolder.", // pid.getValue(), // expectedChange.get(mn).getValue()), // smdMN.getRightsHolder().getValue(), // expectedChange.get(mn).getValue() // ); // testedReplicas.get(pid).add(replica); // tested++; // } // } // catch (BaseException e) { // log.warn("problem getting sysmeta from the mn::" + // e.getClass().getSimpleName() + ": " + e.getDetail_code() + ": " + e.getDescription()); // } // } // test block // } // replica loop // } // object loop // } // time loop // finally report on timed-out tests // Iterator it = testedReplicas.keySet().iterator(); // while (it.hasNext()) { // Identifier pid = it.next(); // for (SystemMetadata sysmeta : cnSmd.get(pid)) { // // // } } @Test public void testSetAccessPolicyUseCase() throws ServiceFailure, InterruptedException { Subject testPerson = setupClientSubject("testPerson"); int totalReplicaCount = 0; // change the systemMetadata on the CN Map expectedChange = new HashMap(); Map cnSmd = new HashMap(); CNode cn = D1Client.getCN(); for (MNode mn : createdObjectMap.keySet()) { currentUrl = mn.getNodeBaseServiceUrl(); Identifier pid = createdObjectMap.get(mn); try { // gather the 'before' info SystemMetadata smdCN = cn.getSystemMetadata(null, pid); // change the sysmeta on the cn if (smdCN.getRightsHolder().equals(testPerson)) { setupClientSubject("testPerson"); } else { setupClientSubject("testRightsHolder"); } String verifiedPermission = pullSMDValue(smdCN,"accessPolicy"); AccessPolicy ap = smdCN.getAccessPolicy(); if (verifiedPermission == null) { ap.addAllow(APITestUtils.buildAccessRule(Constants.SUBJECT_VERIFIED_USER, Permission.READ)); cn.setAccessPolicy(null, pid, ap, smdCN.getSerialVersion().longValue()); expectedChange.put(mn, Permission.READ.toString()); } else if (verifiedPermission.equals(Permission.READ.toString())) { AccessPolicy edittedAP = new AccessPolicy(); for (AccessRule ar: ap.getAllowList()) { if (ar.getSubjectList().contains(D1TypeBuilder.buildSubject(Constants.SUBJECT_VERIFIED_USER))) { List lp = new ArrayList(); lp.add(Permission.WRITE); ar.setPermissionList(lp); } edittedAP.addAllow(ar); } cn.setAccessPolicy(null, pid, edittedAP, smdCN.getSerialVersion().longValue()); expectedChange.put(mn, Permission.WRITE.toString()); } else if (verifiedPermission.equals(Permission.WRITE.toString())) { AccessPolicy edittedAP = new AccessPolicy(); for (AccessRule ar: ap.getAllowList()) { if (ar.getSubjectList().contains(D1TypeBuilder.buildSubject(Constants.SUBJECT_VERIFIED_USER))) { List lp = new ArrayList(); lp.add(Permission.READ); ar.setPermissionList(lp); } edittedAP.addAllow(ar); } cn.setAccessPolicy(null, pid, edittedAP, smdCN.getSerialVersion().longValue()); expectedChange.put(mn, Permission.READ.toString()); // this case shouldn't happen, but you never know.... } else if (verifiedPermission.equals(Permission.CHANGE_PERMISSION.toString())) { AccessPolicy edittedAP = new AccessPolicy(); for (AccessRule ar: ap.getAllowList()) { if (ar.getSubjectList().contains(Constants.SUBJECT_VERIFIED_USER)) { List lp = new ArrayList(); lp.add(Permission.READ); ar.setPermissionList(lp); } edittedAP.addAllow(ar); } cn.setAccessPolicy(null, pid, edittedAP, smdCN.getSerialVersion().longValue()); expectedChange.put(mn, Permission.READ.toString()); } cnSmd.put(pid,smdCN); totalReplicaCount += smdCN.getReplicaList().size(); } catch (BaseException e) { e.printStackTrace(); handleFail(currentUrl,e.getClass().getSimpleName() + ": " + e.getDetail_code() + ": " + e.getDescription()); } } testReplicas(cnSmd,expectedChange,"accessPolicy","the updated sysmeta for '%s' should have Permission '%s' for the verifiedUser subject in the accessPolicy."); } @Test public void testSetReplicationStatusUseCase() throws ServiceFailure { } @Test public void testSetReplicationPolicyUseCase() throws ServiceFailure { } @Test public void testUpdateReplicaMetadataUseCase() throws ServiceFailure { } @Test public void testDeleteReplicaMetadataUseCase() throws ServiceFailure { } private void testReplicas(Map cnSmd, Map expectedChange, String testType, String messageString) throws InterruptedException { int totalReplicaCount = 0; Iterator idit = cnSmd.keySet().iterator(); while (idit.hasNext()) { totalReplicaCount += cnSmd.get(idit.next()).sizeReplicaList(); } log.info("testReplicas().totalReplicaCount = " + totalReplicaCount); Map> testedReplicas = new HashMap>(); // but don't wait forever... long cutoffTime = new Date().getTime() + maxWaitMinutes * 60 * 1000; int tested = 0; while (cutoffTime > new Date().getTime() && tested < totalReplicaCount ) { Thread.sleep(10 * 1000); // iterate through the pids of those successfully altered on the CN Iterator it = expectedChange.keySet().iterator(); while (it.hasNext()) { MNode mn = it.next(); Identifier pid = createdObjectMap.get(mn); log.info("checking replicas of " + pid.getValue()); // check each object's replicas for (Replica replica : cnSmd.get(pid).getReplicaList()) { if (testedReplicas.get(pid) == null) testedReplicas.put(pid, new ArrayList()); if (!testedReplicas.get(pid).contains(replica)) { try { D1Node d1Node = D1Client.getMN(replica.getReplicaMemberNode()); SystemMetadata smdMN = d1Node.getSystemMetadata(null, pid); if (smdMN.getSerialVersion().compareTo(cnSmd.get(pid).getSerialVersion()) > 0) { // BigInts use this idiom for comparison // there's been a change log.info("pid: " + pid.getValue() + ". mn: " + replica.getReplicaMemberNode().getValue() + ". mnRH: " + smdMN.getRightsHolder().getValue()); checkEquals(d1Node.getNodeBaseServiceUrl(), String.format(messageString, pid.getValue(), expectedChange.get(mn)), pullSMDValue(smdMN, testType), expectedChange.get(mn) ); testedReplicas.get(pid).add(replica); tested++; } } catch (BaseException e) { log.warn("problem getting sysmeta from the mn::" + e.getClass().getSimpleName() + ": " + e.getDetail_code() + ": " + e.getDescription()); } } // test block } // replica loop } // object loop } // time loop for (MNode mn : createdObjectMap.keySet()) { currentUrl = mn.getNodeBaseServiceUrl(); Identifier pid = createdObjectMap.get(mn); for (Replica r: cnSmd.get(pid).getReplicaList()) { if (!testedReplicas.get(pid).contains(r)) { // mn didn't have updated sysmetadata, so never tested handleFail(currentUrl,String.format("replica '%s' for pid '%s' did not " + "get updated within test timeframe (%d minutes)", r.getReplicaMemberNode().getValue(), pid.getValue(), maxWaitMinutes)); } } } // return testedReplicas; } private String pullSMDValue(SystemMetadata smd, String type) { if (type.equals("rightsHolder")) return smd.getRightsHolder().getValue(); if (type.equals("setRepPolicyNumReps")) return smd.getReplicationPolicy().getNumberReplicas().toString(); if (type.equals("accessPolicy")) { HashMap> whosGotWhat = AccessUtil.getPermissionMap(smd.getAccessPolicy()); Set perms = whosGotWhat.get(D1TypeBuilder.buildSubject(Constants.SUBJECT_VERIFIED_USER)); // should not have more than one permission, so not going to worry about the order if more than one return StringUtils.join(perms, ","); } return null; } @Override protected String getTestDescription() { return "Tests for the appropriate update in systemMetadata of the Authoritative membernode"; } }