Mercurial > hg > release > thermostat-2.0
view agent/ipc/unix-socket/server/src/test/java/com/redhat/thermostat/agent/ipc/unixsocket/server/internal/UnixSocketServerTransportTest.java @ 2606:f898d30b7b1c
Agent fails to start when -Duser.name is set but not to current OS user
Reviewed-by: jerboaa
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-February/022239.html
PR3233
author | Elliott Baron <ebaron@redhat.com> |
---|---|
date | Thu, 02 Mar 2017 17:20:48 -0500 |
parents | e7fd8adb5f9c |
children |
line wrap: on
line source
/* * Copyright 2012-2017 Red Hat, Inc. * * This file is part of Thermostat. * * Thermostat is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2, or (at your * option) any later version. * * Thermostat is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Thermostat; see the file COPYING. If not see * <http://www.gnu.org/licenses/>. * * Linking this code with other modules is making a combined work * based on this code. Thus, the terms and conditions of the GNU * General Public License cover the whole combination. * * As a special exception, the copyright holders of this code give * you permission to link this code with independent modules to * produce an executable, regardless of the license terms of these * independent modules, and to copy and distribute the resulting * executable under terms of your choice, provided that you also * meet, for each linked independent module, the terms and conditions * of the license of that module. An independent module is a module * which is not derived from or based on this code. If you modify * this code, you may extend this exception to your version of the * library, but you are not obligated to do so. If you do not wish * to do so, delete this exception statement from your version. */ package com.redhat.thermostat.agent.ipc.unixsocket.server.internal; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anySetOf; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.File; import java.io.IOException; import java.nio.channels.spi.AbstractSelector; import java.nio.channels.spi.SelectorProvider; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitor; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.nio.file.attribute.UserPrincipal; import java.util.Set; import java.util.concurrent.ExecutorService; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.redhat.thermostat.agent.ipc.common.internal.IPCProperties; import com.redhat.thermostat.agent.ipc.common.internal.IPCType; import com.redhat.thermostat.agent.ipc.server.ThermostatIPCCallbacks; import com.redhat.thermostat.agent.ipc.unixsocket.common.internal.UnixSocketIPCProperties; import com.redhat.thermostat.agent.ipc.unixsocket.common.internal.UserPrincipalUtils; import com.redhat.thermostat.agent.ipc.unixsocket.server.internal.UnixSocketServerTransport.ChannelUtils; import com.redhat.thermostat.agent.ipc.unixsocket.server.internal.UnixSocketServerTransport.FileUtils; import com.redhat.thermostat.agent.ipc.unixsocket.server.internal.UnixSocketServerTransport.ThreadCreator; public class UnixSocketServerTransportTest { private static final String SERVER_NAME = "test"; private static final String USERNAME = "testUser"; private UnixSocketServerTransport transport; private SelectorProvider provider; private AbstractSelector selector; private ExecutorService execService; private FilenameValidator validator; private FileUtils fileUtils; private Path socketDirPath; private Path ownerDirPath; private AcceptThread acceptThread; private ThreadCreator threadCreator; private FileAttribute<Set<PosixFilePermission>> fileAttr; private Path socketPath; private ThermostatIPCCallbacks callbacks; private ChannelUtils channelUtils; private ThermostatLocalServerSocketChannelImpl channel; private UnixSocketIPCProperties props; private UserPrincipalUtils userUtils; private UserPrincipal currentUser; @SuppressWarnings("unchecked") @Before public void setup() throws Exception { provider = mock(SelectorProvider.class); selector = mock(AbstractSelector.class); when(provider.openSelector()).thenReturn(selector); props = mock(UnixSocketIPCProperties.class); File sockDirFile = mock(File.class); when(props.getSocketDirectory()).thenReturn(sockDirFile); socketDirPath = mock(Path.class); when(socketDirPath.toAbsolutePath()).thenReturn(socketDirPath); when(socketDirPath.normalize()).thenReturn(socketDirPath); when(sockDirFile.toPath()).thenReturn(socketDirPath); ownerDirPath = mock(Path.class); socketPath = mock(Path.class); when(socketDirPath.resolve(USERNAME)).thenReturn(ownerDirPath); File socketFile = mock(File.class); when(props.getSocketFile(SERVER_NAME, USERNAME)).thenReturn(socketFile); when(socketFile.toPath()).thenReturn(socketPath); fileUtils = mock(FileUtils.class); when(fileUtils.exists(socketDirPath)).thenReturn(false); when(fileUtils.getPosixFilePermissions(socketDirPath)).thenReturn(PosixFilePermissions.fromString("rwxr-xr-x")); when(fileUtils.getPosixFilePermissions(ownerDirPath)).thenReturn(PosixFilePermissions.fromString("rwx------")); fileAttr = mock(FileAttribute.class); when(fileUtils.toFileAttribute(any(Set.class))).thenReturn(fileAttr); userUtils = mock(UserPrincipalUtils.class); currentUser = mock(UserPrincipal.class); when(currentUser.getName()).thenReturn(USERNAME); when(userUtils.getCurrentUser()).thenReturn(currentUser); when(fileUtils.getOwner(socketDirPath)).thenReturn(currentUser); when(fileUtils.getOwner(ownerDirPath)).thenReturn(currentUser); execService = mock(ExecutorService.class); validator = mock(FilenameValidator.class); when(validator.validate(any(String.class))).thenReturn(true); acceptThread = mock(AcceptThread.class); threadCreator = mock(ThreadCreator.class); when(threadCreator.createAcceptThread(selector, execService)).thenReturn(acceptThread); channelUtils = mock(ChannelUtils.class); channel = mock(ThermostatLocalServerSocketChannelImpl.class); when(channel.getSocketFile()).thenReturn(socketFile); when(fileUtils.getOwner(socketPath)).thenReturn(currentUser); callbacks = mock(ThermostatIPCCallbacks.class); when(channelUtils.createServerSocketChannel(SERVER_NAME, socketPath, callbacks, props, selector)).thenReturn(channel); transport = new UnixSocketServerTransport(provider, execService, validator, fileUtils, threadCreator, channelUtils, userUtils); } @Test public void testInit() throws Exception { transport.start(props); verify(provider).openSelector(); verify(threadCreator).createAcceptThread(selector, execService); assertEquals(socketDirPath, transport.getSocketDirPath()); } @Test(expected=IOException.class) public void testStartBadProperties() throws Exception { // Not UnixSocketIPCProperties IPCProperties badProps = mock(IPCProperties.class); when(badProps.getType()).thenReturn(IPCType.UNKNOWN); transport = new UnixSocketServerTransport(provider, execService, validator, fileUtils, threadCreator, channelUtils, userUtils); transport.start(badProps); } @Test(expected=IOException.class) public void testInitBadPath() throws Exception { when(socketDirPath.normalize()).thenThrow(new InvalidPathException("TEST", "TEST")); transport = new UnixSocketServerTransport(provider, execService, validator, fileUtils, threadCreator, channelUtils, userUtils); transport.start(props); } @Test public void testPathNormalized() throws Exception { transport.start(props); verify(socketDirPath).toAbsolutePath(); verify(socketDirPath).normalize(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testStartSuccess() throws Exception { transport.start(props); // Verify the socket directory is created with the proper permissions verify(fileUtils).exists(socketDirPath); ArgumentCaptor<Set> permsCaptor = ArgumentCaptor.forClass(Set.class); verify(fileUtils).toFileAttribute(permsCaptor.capture()); Set<PosixFilePermission> perms = (Set<PosixFilePermission>) permsCaptor.getValue(); Set<PosixFilePermission> expectedPerms = PosixFilePermissions.fromString("rwxr-xr-x"); assertEquals(perms, expectedPerms); verify(fileUtils).createDirectory(socketDirPath, fileAttr); // Verify the thread to accept connections is started verify(acceptThread).start(); } @Test public void testStartSuccessDirExists() throws Exception { when(fileUtils.exists(socketDirPath)).thenReturn(true); when(fileUtils.isDirectory(socketDirPath)).thenReturn(true); transport.start(props); // Verify the existing directory is checked and used verify(fileUtils).exists(socketDirPath); verify(fileUtils).isDirectory(socketDirPath); verify(fileUtils).getPosixFilePermissions(socketDirPath); verify(fileUtils, never()).toFileAttribute(anySetOf(PosixFilePermission.class)); verify(fileUtils, never()).createDirectory(any(Path.class)); // Verify the thread to accept connections is started verify(acceptThread).start(); } @Test public void testStartFailsNotDir() throws Exception { when(fileUtils.exists(socketDirPath)).thenReturn(true); // Socket directory is not a directory when(fileUtils.isDirectory(socketDirPath)).thenReturn(false); try { transport.start(props); fail("Expected IOException"); } catch (IOException e) { verify(fileUtils, never()).createDirectory(any(Path.class)); verify(acceptThread, never()).start(); } } @Test public void testStartCreateParent() throws Exception { Path sockDirParent = mock(Path.class); when(socketDirPath.getParent()).thenReturn(sockDirParent); transport.start(props); verify(fileUtils).createDirectories(sockDirParent); } @Test(expected=IOException.class) public void testStartBadOwner() throws Exception { UserPrincipal badPrincipal = mock(UserPrincipal.class); when(fileUtils.getOwner(socketDirPath)).thenReturn(badPrincipal); transport.start(props); } @Test(expected=IOException.class) public void testStartOwnerCheckUnsupported() throws Exception { when(fileUtils.getOwner(socketDirPath)).thenThrow(new UnsupportedOperationException()); transport.start(props); } @Test(expected=IOException.class) public void testStartOwnerCheckNullOwner() throws Exception { when(fileUtils.getOwner(socketDirPath)).thenReturn(null); transport.start(props); } @Test public void testStartFailsBadPerm() throws Exception { when(fileUtils.exists(socketDirPath)).thenReturn(true); when(fileUtils.isDirectory(socketDirPath)).thenReturn(true); // Socket directory is world readable when(fileUtils.getPosixFilePermissions(socketDirPath)).thenReturn(PosixFilePermissions.fromString("rwxrwxr-x")); try { transport.start(props); fail("Expected IOException"); } catch (IOException e) { verify(fileUtils, never()).createDirectory(any(Path.class)); verify(acceptThread, never()).start(); } } @Test public void testShutdownSuccess() throws Exception { transport.start(props); mockSocketDirOnShutdown(); transport.shutdown(); verify(acceptThread).shutdown(); verify(channelUtils).closeSelector(selector); // Check socket directory is removed verify(fileUtils).delete(socketPath); verify(fileUtils).delete(socketDirPath); } @Test public void testShutdownFailure() throws Exception { transport.start(props); mockSocketDirOnShutdown(); doThrow(new IOException()).when(acceptThread).shutdown(); try { transport.shutdown(); fail("Expected IO Exception"); } catch (IOException e) { // Socket directory should still be deleted verify(fileUtils).delete(socketPath); verify(fileUtils).delete(socketDirPath); } } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testCreateServer() throws Exception { transport.start(props); transport.createServer(SERVER_NAME, callbacks); // Verify the user-specific socket directory is created with the proper permissions verify(fileUtils).exists(ownerDirPath); ArgumentCaptor<Set> permsCaptor = ArgumentCaptor.forClass(Set.class); verify(fileUtils, times(2)).toFileAttribute(permsCaptor.capture()); // First invocation is for top-level socket dir, second is for user-specific dir Set<PosixFilePermission> perms = (Set<PosixFilePermission>) permsCaptor.getAllValues().get(1); Set<PosixFilePermission> expectedPerms = PosixFilePermissions.fromString("rwx------"); assertEquals(perms, expectedPerms); verify(fileUtils).createDirectory(ownerDirPath, fileAttr); verify(fileUtils).getPosixFilePermissions(ownerDirPath); verify(fileUtils).setOwner(ownerDirPath, currentUser); verify(fileUtils).exists(socketPath); verify(fileUtils, never()).delete(socketPath); checkChannel(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testCreateServerWithOwner() throws Exception { String otherUsername = "otherUser"; UserPrincipal owner = mock(UserPrincipal.class); when(owner.getName()).thenReturn(otherUsername); Path otherOwnerDirPath = mock(Path.class); when(socketDirPath.resolve(otherUsername)).thenReturn(otherOwnerDirPath); when(fileUtils.getPosixFilePermissions(otherOwnerDirPath)).thenReturn(PosixFilePermissions.fromString("rwx------")); when(fileUtils.getOwner(otherOwnerDirPath)).thenReturn(owner); Path otherSocketPath = mock(Path.class); when(fileUtils.getOwner(otherSocketPath)).thenReturn(owner); File otherSocketFile = mock(File.class); when(props.getSocketFile(SERVER_NAME, otherUsername)).thenReturn(otherSocketFile); ThermostatLocalServerSocketChannelImpl otherChannel = mock(ThermostatLocalServerSocketChannelImpl.class); when(channelUtils.createServerSocketChannel(SERVER_NAME, otherSocketPath, callbacks, props, selector)).thenReturn(otherChannel); when(otherChannel.getSocketFile()).thenReturn(otherSocketFile); when(otherSocketFile.toPath()).thenReturn(otherSocketPath); transport.start(props); transport.createServer(SERVER_NAME, callbacks, owner); // Verify the user-specific socket directory is created with the proper permissions verify(fileUtils).exists(otherOwnerDirPath); ArgumentCaptor<Set> permsCaptor = ArgumentCaptor.forClass(Set.class); verify(fileUtils, times(2)).toFileAttribute(permsCaptor.capture()); // First invocation is for top-level socket dir, second is for user-specific dir Set<PosixFilePermission> perms = (Set<PosixFilePermission>) permsCaptor.getAllValues().get(1); Set<PosixFilePermission> expectedPerms = PosixFilePermissions.fromString("rwx------"); assertEquals(perms, expectedPerms); verify(fileUtils).createDirectory(otherOwnerDirPath, fileAttr); verify(fileUtils).getPosixFilePermissions(otherOwnerDirPath); verify(fileUtils).setOwner(otherOwnerDirPath, owner); verify(fileUtils).exists(otherSocketPath); verify(fileUtils, never()).delete(otherSocketPath); verify(channelUtils).createServerSocketChannel(SERVER_NAME, otherSocketPath, callbacks, props, selector); verify(fileUtils).setOwner(otherSocketPath, owner); ThermostatLocalServerSocketChannelImpl result = transport.getSockets().get(SERVER_NAME); assertEquals(otherChannel, result); } @Test public void testCreateServerOwnerDirExists() throws Exception { transport.start(props); when(fileUtils.exists(ownerDirPath)).thenReturn(true); transport.createServer(SERVER_NAME, callbacks); verify(fileUtils, never()).createDirectory(eq(ownerDirPath), any(FileAttribute[].class)); verify(fileUtils, never()).setOwner(eq(ownerDirPath), any(UserPrincipal.class)); // Should still check permissions verify(fileUtils).getPosixFilePermissions(ownerDirPath); verify(fileUtils).exists(socketPath); verify(fileUtils, never()).delete(socketPath); checkChannel(); } private void checkChannel() throws IOException { verify(channelUtils).createServerSocketChannel(SERVER_NAME, socketPath, callbacks, props, selector); verify(fileUtils).setOwner(socketPath, currentUser); ThermostatLocalServerSocketChannelImpl result = transport.getSockets().get(SERVER_NAME); assertEquals(channel, result); } @Test public void testCreateServerLeftoverFile() throws Exception { transport.start(props); when(fileUtils.exists(socketPath)).thenReturn(true); transport.createServer(SERVER_NAME, callbacks); verify(fileUtils).exists(socketPath); verify(fileUtils).delete(socketPath); checkChannel(); } @Test(expected=IOException.class) public void testCreateServerServerExists() throws Exception { ThermostatLocalServerSocketChannelImpl channel = mock(ThermostatLocalServerSocketChannelImpl.class); transport.getSockets().put(SERVER_NAME, channel); transport.createServer(SERVER_NAME, callbacks); } @Test(expected=IOException.class) public void testCreateServerInvalidName() throws Exception { when(validator.validate(SERVER_NAME)).thenReturn(false); transport.createServer(SERVER_NAME, callbacks); } @Test(expected=IOException.class) public void testCreateServerSocketDirPermsChanged() throws Exception { transport.start(props); when(fileUtils.getPosixFilePermissions(socketDirPath)).thenReturn(PosixFilePermissions.fromString("rwxrwxrwx")); transport.createServer(SERVER_NAME, callbacks); } @Test(expected=IOException.class) public void testCreateServerOwnerDirPermsChanged() throws Exception { transport.start(props); when(fileUtils.getPosixFilePermissions(ownerDirPath)).thenReturn(PosixFilePermissions.fromString("rwxrwxrwx")); transport.createServer(SERVER_NAME, callbacks); } @Test(expected=IOException.class) public void testCreateServerSocketDirOwnerChanged() throws Exception { transport.start(props); UserPrincipal badPrincipal = mock(UserPrincipal.class); when(fileUtils.getOwner(socketDirPath)).thenReturn(badPrincipal); transport.createServer(SERVER_NAME, callbacks); } @Test(expected=IOException.class) public void testCreateServerOwnerDirOwnerChanged() throws Exception { transport.start(props); UserPrincipal badPrincipal = mock(UserPrincipal.class); when(fileUtils.getOwner(ownerDirPath)).thenReturn(badPrincipal); transport.createServer(SERVER_NAME, callbacks); } @Test(expected=IOException.class) public void testCreateServerBadSocketOwner() throws Exception { transport.start(props); UserPrincipal badPrincipal = mock(UserPrincipal.class); when(fileUtils.getOwner(socketPath)).thenReturn(badPrincipal); transport.createServer(SERVER_NAME, callbacks); } @Test public void testServerExists() throws Exception { assertFalse(transport.serverExists(SERVER_NAME)); transport.getSockets().put(SERVER_NAME, channel); assertTrue(transport.serverExists(SERVER_NAME)); } @Test public void testDestroyServer() throws Exception { transport.getSockets().put(SERVER_NAME, channel); transport.destroyServer(SERVER_NAME); verify(channel).close(); verify(fileUtils).delete(socketPath); } @Test(expected=IOException.class) public void testDestroyServerNotExist() throws Exception { transport.destroyServer(SERVER_NAME); } @Test public void testDestroyServerCloseFails() throws Exception { doThrow(new IOException()).when(channel).close(); transport.getSockets().put(SERVER_NAME, channel); try { transport.destroyServer(SERVER_NAME); fail("Expected IOException"); } catch (IOException e) { // Socket file should still be deleted verify(fileUtils).delete(socketPath); } } // Mock a socket directory containing a socket file @SuppressWarnings("unchecked") private void mockSocketDirOnShutdown() throws IOException { when(fileUtils.exists(socketDirPath)).thenReturn(true); when(fileUtils.walkFileTree(eq(socketDirPath), anySetOf(FileVisitOption.class), eq(2), any(FileVisitor.class))).thenAnswer(new Answer<Path>() { @Override public Path answer(InvocationOnMock invocation) throws Throwable { FileVisitor<? super Path> visitor = (FileVisitor<? super Path>) invocation.getArguments()[3]; // Invoke each of the methods we override once visitor.visitFile(socketPath, null); visitor.postVisitDirectory(socketDirPath, null); return socketDirPath; } }); } }