2828import static org .junit .jupiter .api .Assertions .assertTrue ;
2929import static org .junit .jupiter .api .Assertions .fail ;
3030import static org .junit .jupiter .api .Assumptions .assumeTrue ;
31+ import static org .mockito .Mockito .doReturn ;
3132import static org .mockito .Mockito .mock ;
33+ import static org .mockito .Mockito .spy ;
3234import static org .mockito .Mockito .when ;
3335import static org .mockito .ArgumentMatchers .anyString ;
3436import static org .mockito .ArgumentMatchers .any ;
@@ -122,6 +124,8 @@ public class TestAuxServices {
122124 .getSimpleName ());
123125 private File manifest = new File (rootDir , "manifest.txt" );
124126 private ObjectMapper mapper = new ObjectMapper ();
127+ private static final FsPermission WRITABLE_BY_OWNER = FsPermission .createImmutable ((short ) 0755 );
128+ private static final FsPermission WRITABLE_BY_GROUP = FsPermission .createImmutable ((short ) 0775 );
125129
126130 public static Collection <Boolean > getParams () {
127131 return Arrays .asList (false , true );
@@ -266,6 +270,24 @@ private void writeManifestFile(AuxServiceRecords services, Configuration
266270 mapper .writeValue (manifest , services );
267271 }
268272
273+ /**
274+ * Creates a spy object of AuxServices for test cases which assume that we have proper
275+ * file system permissions by default.
276+ *
277+ * Permission checking iterates through the parents of the manifest file until it
278+ * reaches the system root, so without mocking this the success of the initialization
279+ * would heavily depend on the environment where the test is running.
280+ *
281+ * @return a spy object of AuxServices
282+ */
283+ private AuxServices getSpyAuxServices (AuxiliaryLocalPathHandler auxiliaryLocalPathHandler ,
284+ Context nmContext , DeletionService deletionService ) throws IOException {
285+ AuxServices auxServices = spy (new AuxServices (auxiliaryLocalPathHandler ,
286+ nmContext , deletionService ));
287+ doReturn (true ).when (auxServices ).checkManifestPermissions (any (FileStatus .class ));
288+ return auxServices ;
289+ }
290+
269291 @ SuppressWarnings ("resource" )
270292 @ ParameterizedTest
271293 @ MethodSource ("getParams" )
@@ -317,7 +339,7 @@ public void testRemoteAuxServiceClassPath(boolean pUseManifest) throws Exception
317339 YarnConfiguration .NM_AUX_SERVICE_REMOTE_CLASSPATH , "ServiceC" ),
318340 testJar .getAbsolutePath ());
319341 }
320- aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
342+ aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
321343 mockContext2 , mockDelService2 );
322344 aux .init (conf );
323345 fail ("The permission of the jar is wrong."
@@ -339,7 +361,7 @@ public void testRemoteAuxServiceClassPath(boolean pUseManifest) throws Exception
339361 YarnConfiguration .NM_AUX_SERVICE_REMOTE_CLASSPATH , "ServiceC" ),
340362 testJar .getAbsolutePath ());
341363 }
342- aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
364+ aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
343365 mockContext2 , mockDelService2 );
344366 aux .init (conf );
345367 aux .start ();
@@ -356,7 +378,7 @@ public void testRemoteAuxServiceClassPath(boolean pUseManifest) throws Exception
356378
357379 // initialize the same auxservice again, and make sure that we did not
358380 // re-download the jar from remote directory.
359- aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
381+ aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
360382 mockContext2 , mockDelService2 );
361383 aux .init (conf );
362384 aux .start ();
@@ -377,7 +399,7 @@ public void testRemoteAuxServiceClassPath(boolean pUseManifest) throws Exception
377399 FileTime fileTime = FileTime .fromMillis (time );
378400 Files .setLastModifiedTime (Paths .get (testJar .getAbsolutePath ()),
379401 fileTime );
380- aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
402+ aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
381403 mockContext2 , mockDelService2 );
382404 aux .init (conf );
383405 aux .start ();
@@ -419,7 +441,7 @@ public void testCustomizedAuxServiceClassPath(boolean pUseManifest) throws Excep
419441 "ServiceC" ), ServiceC .class , Service .class );
420442 }
421443 @ SuppressWarnings ("resource" )
422- AuxServices aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
444+ AuxServices aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
423445 MOCK_CONTEXT , MOCK_DEL_SERVICE );
424446 aux .init (conf );
425447 aux .start ();
@@ -472,7 +494,7 @@ public void testCustomizedAuxServiceClassPath(boolean pUseManifest) throws Excep
472494 when (mockDirsHandler .getLocalPathForWrite (anyString ())).thenReturn (
473495 rootAuxServiceDirPath );
474496 when (mockContext2 .getLocalDirsHandler ()).thenReturn (mockDirsHandler );
475- aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
497+ aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
476498 mockContext2 , MOCK_DEL_SERVICE );
477499 aux .init (conf );
478500 aux .start ();
@@ -654,7 +676,7 @@ private Configuration getABConf(String aName, String bName,
654676 public void testAuxServices (boolean pUseManifest ) throws IOException {
655677 initTestAuxServices (pUseManifest );
656678 Configuration conf = getABConf ();
657- final AuxServices aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
679+ final AuxServices aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
658680 MOCK_CONTEXT , MOCK_DEL_SERVICE );
659681 aux .init (conf );
660682
@@ -684,7 +706,7 @@ public void testAuxServices(boolean pUseManifest) throws IOException {
684706 public void testAuxServicesMeta (boolean pUseManifest ) throws IOException {
685707 initTestAuxServices (pUseManifest );
686708 Configuration conf = getABConf ();
687- final AuxServices aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
709+ final AuxServices aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
688710 MOCK_CONTEXT , MOCK_DEL_SERVICE );
689711 aux .init (conf );
690712
@@ -718,7 +740,7 @@ public void testAuxUnexpectedStop(boolean pUseManifest) throws IOException {
718740 initTestAuxServices (pUseManifest );
719741 // AuxServices no longer expected to stop when services stop
720742 Configuration conf = getABConf ();
721- final AuxServices aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
743+ final AuxServices aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
722744 MOCK_CONTEXT , MOCK_DEL_SERVICE );
723745 aux .init (conf );
724746 aux .start ();
@@ -736,7 +758,7 @@ public void testValidAuxServiceName(boolean pUseManifest) throws IOException {
736758 initTestAuxServices (pUseManifest );
737759 Configuration conf = getABConf ("Asrv1" , "Bsrv_2" , ServiceA .class ,
738760 ServiceB .class );
739- final AuxServices aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
761+ final AuxServices aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
740762 MOCK_CONTEXT , MOCK_DEL_SERVICE );
741763 try {
742764 aux .init (conf );
@@ -745,7 +767,7 @@ public void testValidAuxServiceName(boolean pUseManifest) throws IOException {
745767 }
746768
747769 //Test bad auxService Name
748- final AuxServices aux1 = new AuxServices (MOCK_AUX_PATH_HANDLER ,
770+ final AuxServices aux1 = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
749771 MOCK_CONTEXT , MOCK_DEL_SERVICE );
750772 if (useManifest ) {
751773 AuxServiceRecord serviceA =
@@ -776,7 +798,7 @@ public void testAuxServiceRecoverySetup(boolean pUseManifest) throws IOException
776798 conf .setBoolean (YarnConfiguration .NM_RECOVERY_ENABLED , true );
777799 conf .set (YarnConfiguration .NM_RECOVERY_DIR , TEST_DIR .toString ());
778800 try {
779- final AuxServices aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
801+ final AuxServices aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
780802 MOCK_CONTEXT , MOCK_DEL_SERVICE );
781803 aux .init (conf );
782804 assertEquals (2 , aux .getServices ().size ());
@@ -906,62 +928,130 @@ public void testAuxServicesConfChange(boolean pUseManifest) throws IOException {
906928
907929 @ ParameterizedTest
908930 @ MethodSource ("getParams" )
909- public void testAuxServicesManifestPermissions (boolean pUseManifest ) throws IOException {
931+ public void testAuxServicesInitWithManifestOwnerAndPermissionCheck (boolean pUseManifest )
932+ throws IOException {
910933 initTestAuxServices (pUseManifest );
911934 assumeTrue (useManifest );
912935 Configuration conf = getABConf ();
913- FileSystem fs = FileSystem .get (conf );
914- fs .setPermission (new Path (manifest .getAbsolutePath ()), FsPermission
915- .createImmutable ((short ) 0777 ));
916- AuxServices aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
917- MOCK_CONTEXT , MOCK_DEL_SERVICE );
936+ AuxServices aux = spy (new AuxServices (MOCK_AUX_PATH_HANDLER ,
937+ MOCK_CONTEXT , MOCK_DEL_SERVICE ));
938+ doReturn (false ).when (aux ).checkManifestPermissions (any (FileStatus .class ));
918939 aux .init (conf );
919940 assertEquals (0 , aux .getServices ().size ());
920941
921- fs .setPermission (new Path (manifest .getAbsolutePath ()), FsPermission
922- .createImmutable ((short ) 0775 ));
923- aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
924- MOCK_CONTEXT , MOCK_DEL_SERVICE );
925- aux .init (conf );
926- assertEquals (0 , aux .getServices ().size ());
927-
928- fs .setPermission (new Path (manifest .getAbsolutePath ()), FsPermission
929- .createImmutable ((short ) 0755 ));
930- fs .setPermission (new Path (rootDir .getAbsolutePath ()), FsPermission
931- .createImmutable ((short ) 0775 ));
932- aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
933- MOCK_CONTEXT , MOCK_DEL_SERVICE );
934- aux .init (conf );
935- assertEquals (0 , aux .getServices ().size ());
936-
937- fs .setPermission (new Path (rootDir .getAbsolutePath ()), FsPermission
938- .createImmutable ((short ) 0755 ));
939- aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
942+ aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
940943 MOCK_CONTEXT , MOCK_DEL_SERVICE );
941944 aux .init (conf );
942945 assertEquals (2 , aux .getServices ().size ());
943946
944947 conf .set (YarnConfiguration .YARN_ADMIN_ACL , "" );
945- aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
948+ aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
946949 MOCK_CONTEXT , MOCK_DEL_SERVICE );
947950 aux .init (conf );
948951 assertEquals (0 , aux .getServices ().size ());
949952
950953 conf .set (YarnConfiguration .YARN_ADMIN_ACL , UserGroupInformation
951954 .getCurrentUser ().getShortUserName ());
952- aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
955+ aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
953956 MOCK_CONTEXT , MOCK_DEL_SERVICE );
954957 aux .init (conf );
955958 assertEquals (2 , aux .getServices ().size ());
956959 }
957960
961+ @ ParameterizedTest
962+ @ MethodSource ("getParams" )
963+ public void testCheckManifestPermissionsWhenFileIsOnlyWritableByOwner (boolean pUseManifest )
964+ throws IOException {
965+ initTestAuxServices (pUseManifest );
966+ assumeTrue (useManifest );
967+ final AuxServices aux = spy (new AuxServices (MOCK_AUX_PATH_HANDLER ,
968+ MOCK_CONTEXT , MOCK_DEL_SERVICE ));
969+ FileStatus manifestFileStatus = mock (FileStatus .class );
970+ Path manifestPath = mock (Path .class );
971+
972+ when (manifestFileStatus .getPermission ()).thenReturn (WRITABLE_BY_OWNER );
973+ when (manifestFileStatus .getPath ()).thenReturn (manifestPath );
974+
975+ assertTrue (aux .checkManifestPermissions (manifestFileStatus ));
976+ }
977+
978+ @ ParameterizedTest
979+ @ MethodSource ("getParams" )
980+ public void testCheckManifestPermissionsWhenFileIsWritableByGroup (boolean pUseManifest )
981+ throws IOException {
982+ initTestAuxServices (pUseManifest );
983+ assumeTrue (useManifest );
984+ final AuxServices aux = spy (new AuxServices (MOCK_AUX_PATH_HANDLER ,
985+ MOCK_CONTEXT , MOCK_DEL_SERVICE ));
986+ FileStatus manifestFileStatus = mock (FileStatus .class );
987+ Path manifestPath = mock (Path .class );
988+
989+ when (manifestFileStatus .getPermission ()).thenReturn (WRITABLE_BY_GROUP );
990+ when (manifestFileStatus .getPath ()).thenReturn (manifestPath );
991+
992+ assertFalse (aux .checkManifestPermissions (manifestFileStatus ));
993+ }
994+
995+ @ ParameterizedTest
996+ @ MethodSource ("getParams" )
997+ public void testCheckManifestPermissionsWhenParentIsWritableByGroup (boolean pUseManifest )
998+ throws IOException {
999+ initTestAuxServices (pUseManifest );
1000+ assumeTrue (useManifest );
1001+ final AuxServices aux = spy (new AuxServices (MOCK_AUX_PATH_HANDLER ,
1002+ MOCK_CONTEXT , MOCK_DEL_SERVICE ));
1003+
1004+ FileStatus manifestFileStatus = mock (FileStatus .class );
1005+ FileStatus parentFolderStatus = mock (FileStatus .class );
1006+ when (manifestFileStatus .getPermission ()).thenReturn (WRITABLE_BY_OWNER );
1007+ when (parentFolderStatus .getPermission ()).thenReturn (WRITABLE_BY_GROUP );
1008+
1009+ Path manifestPath = mock (Path .class );
1010+ Path parentPath = mock (Path .class );
1011+ when (manifestFileStatus .getPath ()).thenReturn (manifestPath );
1012+ when (manifestPath .getParent ()).thenReturn (parentPath );
1013+
1014+ FileSystem manifestFs = mock (FileSystem .class );
1015+ when (manifestFs .getFileStatus (parentPath )).thenReturn (parentFolderStatus );
1016+ doReturn (manifestFs ).when (aux ).getManifestFS ();
1017+
1018+ assertFalse (aux .checkManifestPermissions (manifestFileStatus ));
1019+ }
1020+
1021+ @ ParameterizedTest
1022+ @ MethodSource ("getParams" )
1023+ public void testCheckManifestPermissionsWhenParentAndFileIsWritableByOwner (boolean pUseManifest )
1024+ throws IOException {
1025+ initTestAuxServices (pUseManifest );
1026+ assumeTrue (useManifest );
1027+ final AuxServices aux = spy (new AuxServices (MOCK_AUX_PATH_HANDLER ,
1028+ MOCK_CONTEXT , MOCK_DEL_SERVICE ));
1029+
1030+ FileStatus manifestFileStatus = mock (FileStatus .class );
1031+ FileStatus parentFolderStatus = mock (FileStatus .class );
1032+ when (manifestFileStatus .getPermission ()).thenReturn (WRITABLE_BY_OWNER );
1033+ when (parentFolderStatus .getPermission ()).thenReturn (WRITABLE_BY_OWNER );
1034+
1035+ Path manifestPath = mock (Path .class );
1036+ Path parentPath = mock (Path .class );
1037+ when (manifestFileStatus .getPath ()).thenReturn (manifestPath );
1038+ when (parentFolderStatus .getPath ()).thenReturn (parentPath );
1039+ when (manifestPath .getParent ()).thenReturn (parentPath );
1040+
1041+ FileSystem manifestFs = mock (FileSystem .class );
1042+ when (manifestFs .getFileStatus (parentPath )).thenReturn (parentFolderStatus );
1043+ doReturn (manifestFs ).when (aux ).getManifestFS ();
1044+
1045+ assertTrue (aux .checkManifestPermissions (manifestFileStatus ));
1046+ }
1047+
9581048 @ ParameterizedTest
9591049 @ MethodSource ("getParams" )
9601050 public void testRemoveManifest (boolean pUseManifest ) throws IOException {
9611051 initTestAuxServices (pUseManifest );
9621052 assumeTrue (useManifest );
9631053 Configuration conf = getABConf ();
964- final AuxServices aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
1054+ final AuxServices aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
9651055 MOCK_CONTEXT , MOCK_DEL_SERVICE );
9661056 aux .init (conf );
9671057 assertEquals (2 , aux .getServices ().size ());
@@ -976,7 +1066,7 @@ public void testManualReload(boolean pUseManifest) throws IOException {
9761066 initTestAuxServices (pUseManifest );
9771067 assumeTrue (useManifest );
9781068 Configuration conf = getABConf ();
979- final AuxServices aux = new AuxServices (MOCK_AUX_PATH_HANDLER ,
1069+ final AuxServices aux = getSpyAuxServices (MOCK_AUX_PATH_HANDLER ,
9801070 MOCK_CONTEXT , MOCK_DEL_SERVICE );
9811071 aux .init (conf );
9821072 try {
0 commit comments