azkaban-memoizeit

Merge branch 'release-2.1'

5/8/2013 3:31:50 AM

Details

build.xml 10(+8 -2)

diff --git a/build.xml b/build.xml
index e48342a..244b7e4 100644
--- a/build.xml
+++ b/build.xml
@@ -71,12 +71,18 @@
 		<delete dir="${dist.sql.package.dir}" />
 		<mkdir dir="${dist.sql.package.dir}" />
 
+		<concat destfile="${dist.sql.package.dir}/create-all-sql-${version}.sql" fixlastline="yes">
+			<fileset dir="${sql.src.dir}" >
+				<exclude name="update*.sql"/>
+			</fileset>	
+		</concat>
+		
 		<copy todir="${dist.sql.package.dir}" >
 			<fileset dir="${sql.src.dir}" />
 		</copy>
 
-                <tar destfile="${dist.sql.package.dir}/${name}-sql-script-${version}.tar.gz" compression="gzip" longfile="gnu">
-                	<tarfileset dir="${dist.sql.package.dir}" prefix="azkaban-${version}" filemode="755" />
+        <tar destfile="${dist.sql.package.dir}/${name}-sql-script-${version}.tar.gz" compression="gzip" longfile="gnu">
+        	<tarfileset dir="${dist.sql.package.dir}" prefix="azkaban-${version}" filemode="755" />
 		</tar>
 
 	</target>
diff --git a/src/java/azkaban/execapp/event/FlowWatcher.java b/src/java/azkaban/execapp/event/FlowWatcher.java
index 2e4f576..4a13a4b 100644
--- a/src/java/azkaban/execapp/event/FlowWatcher.java
+++ b/src/java/azkaban/execapp/event/FlowWatcher.java
@@ -10,7 +10,7 @@ import azkaban.executor.ExecutableNode;
 import azkaban.executor.Status;
 
 public abstract class FlowWatcher {
-	private static final Logger logger = Logger.getLogger(FlowWatcher.class);
+	private Logger logger;
 	
 	private int execId;
 	private ExecutableFlow flow;
@@ -25,15 +25,19 @@ public abstract class FlowWatcher {
 		this.flow = flow;
 	}
 	
+	public void setLogger(Logger logger) {
+		this.logger = logger;
+	}
+	
+	protected Logger getLogger() {
+		return this.logger;
+	}
+	
 	/**
 	 * Called to fire events to the JobRunner listeners
 	 * @param jobId
 	 */
 	protected synchronized void handleJobFinished(String jobId, Status status) {
-		if (cancelWatch) {
-			return;
-		}
-
 		BlockingStatus block = map.get(jobId);
 		if (block != null) {
 			block.changeStatus(status);
diff --git a/src/java/azkaban/execapp/event/LocalFlowWatcher.java b/src/java/azkaban/execapp/event/LocalFlowWatcher.java
index ea78174..afe9248 100644
--- a/src/java/azkaban/execapp/event/LocalFlowWatcher.java
+++ b/src/java/azkaban/execapp/event/LocalFlowWatcher.java
@@ -3,6 +3,7 @@ package azkaban.execapp.event;
 
 import azkaban.execapp.FlowRunner;
 import azkaban.execapp.JobRunner;
+import azkaban.execapp.event.Event.Type;
 import azkaban.executor.ExecutableNode;
 
 public class LocalFlowWatcher extends FlowWatcher {
@@ -30,17 +31,30 @@ public class LocalFlowWatcher extends FlowWatcher {
 		runner.removeListener(watcherListener);
 		runner = null;
 		
+		getLogger().info("Stopping watcher, and unblocking pipeline");
 		super.failAllWatches();
 	}
 
 	public class LocalFlowWatcherListener implements EventListener {
 		@Override
 		public void handleEvent(Event event) {
-			if (event.getRunner() instanceof JobRunner) {
-				JobRunner runner = (JobRunner)event.getRunner();
-				ExecutableNode node = runner.getNode();
-				
-				handleJobFinished(node.getJobId(), node.getStatus());
+			if (event.getType() == Type.JOB_FINISHED) {
+				if (event.getRunner() instanceof FlowRunner) {
+					Object data = event.getData();
+					if (data instanceof ExecutableNode) {
+						ExecutableNode node = (ExecutableNode)data;
+						handleJobFinished(node.getJobId(), node.getStatus());
+					}
+				}
+				else if (event.getRunner() instanceof JobRunner) {
+					JobRunner runner = (JobRunner)event.getRunner();
+					ExecutableNode node = runner.getNode();
+					
+					handleJobFinished(node.getJobId(), node.getStatus());
+				}
+			}
+			else if (event.getType() == Type.FLOW_FINISHED) {
+				stopWatcher();
 			}
 		}
 	}
diff --git a/src/java/azkaban/execapp/FlowRunner.java b/src/java/azkaban/execapp/FlowRunner.java
index f6f9612..005e720 100644
--- a/src/java/azkaban/execapp/FlowRunner.java
+++ b/src/java/azkaban/execapp/FlowRunner.java
@@ -4,9 +4,9 @@ import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -40,7 +40,7 @@ import azkaban.utils.PropsUtils;
 public class FlowRunner extends EventHandler implements Runnable {
 	private static final Layout DEFAULT_LAYOUT = new PatternLayout("%d{dd-MM-yyyy HH:mm:ss z} %c{1} %p - %m\n");
 	// We check update every 5 minutes, just in case things get stuck. But for the most part, we'll be idling.
-	private static final long CHECK_WAIT_MS = 5*60*60*1000;
+	private static final long CHECK_WAIT_MS = 5*60*1000;
 	
 	private Logger logger;
 	private Layout loggerLayout = DEFAULT_LAYOUT;
@@ -65,6 +65,7 @@ public class FlowRunner extends EventHandler implements Runnable {
 	private Map<String, Props> jobOutputProps = new HashMap<String, Props>();
 	
 	private Props globalProps;
+	private Props commonProps;
 	private final JobTypeManager jobtypeManager;
 	
 	private JobRunnerEventListener listener = new JobRunnerEventListener();
@@ -78,7 +79,7 @@ public class FlowRunner extends EventHandler implements Runnable {
 	// Watches external flows for execution.
 	private FlowWatcher watcher = null;
 
-	private HashSet<String> proxyUsers = null;
+	private Set<String> proxyUsers = null;
 	private boolean validateUserProxy;
 	
 	private String jobLogFileSize = "5MB";
@@ -94,7 +95,6 @@ public class FlowRunner extends EventHandler implements Runnable {
 		this.flow = flow;
 		this.executorLoader = executorLoader;
 		this.projectLoader = projectLoader;
-		this.executorService = Executors.newFixedThreadPool(numJobThreads);
 		this.execDir = new File(flow.getExecutionPath());
 		this.jobtypeManager = jobtypeManager;
 
@@ -115,6 +115,11 @@ public class FlowRunner extends EventHandler implements Runnable {
 		return this;
 	}
 	
+	public FlowRunner setNumJobThreads(int jobs) {
+		numJobThreads = jobs;
+		return this;
+	}
+	
 	public FlowRunner setJobLogSettings(String jobLogFileSize, int jobLogNumFiles) {
 		this.jobLogFileSize = jobLogFileSize;
 		this.jobLogNumFiles = jobLogNumFiles;
@@ -133,6 +138,9 @@ public class FlowRunner extends EventHandler implements Runnable {
 	
 	public void run() {
 		try {
+			if (this.executorService == null) {
+				this.executorService = Executors.newFixedThreadPool(numJobThreads);
+			}
 			setupFlowExecution();
 			flow.setStartTime(System.currentTimeMillis());
 			
@@ -153,7 +161,9 @@ public class FlowRunner extends EventHandler implements Runnable {
 		}
 		finally {
 			if (watcher != null) {
+				logger.info("Watcher is attached. Stopping watcher.");
 				watcher.stopWatcher();
+				logger.info("Watcher cancelled status is " + watcher.isWatchCancelled());
 			}
 
 			flow.setEndTime(System.currentTimeMillis());
@@ -171,10 +181,15 @@ public class FlowRunner extends EventHandler implements Runnable {
 		String flowId = flow.getFlowId();
 		
 		// Add a bunch of common azkaban properties
-		PropsUtils.addCommonFlowProperties(flow);
+		commonProps = PropsUtils.addCommonFlowProperties(flow);
 		
 		// Create execution dir
 		createLogger(flowId);
+		
+		if (this.watcher != null) {
+			this.watcher.setLogger(logger);
+		}
+		
 		logger.info("Running execid:" + execId + " flow:" + flowId + " project:" + projectId + " version:" + version);
 		if (pipelineExecId != null) {
 			logger.info("Running simulateously with " + pipelineExecId + ". Pipelining level " + pipelineLevel);
@@ -289,7 +304,7 @@ public class FlowRunner extends EventHandler implements Runnable {
 				else {
 					List<ExecutableNode> jobsReadyToRun = findReadyJobsToRun();
 					
-					if (!jobsReadyToRun.isEmpty()) {
+					if (!jobsReadyToRun.isEmpty() && !flowCancelled) {
 						for (ExecutableNode node : jobsReadyToRun) {
 							long currentTime = System.currentTimeMillis();
 							
@@ -313,19 +328,21 @@ public class FlowRunner extends EventHandler implements Runnable {
 								logger.info("Killing " + node.getJobId() + " due to prior errors.");
 								node.setStartTime(currentTime);
 								node.setEndTime(currentTime);
+								fireEventListeners(Event.create(this, Type.JOB_FINISHED, node));
 							} // If disabled, then we auto skip
 							else if (node.getStatus() == Status.DISABLED) {
 								logger.info("Skipping disabled job " + node.getJobId() + ".");
 								node.setStartTime(currentTime);
 								node.setEndTime(currentTime);
 								node.setStatus(Status.SKIPPED);
+								fireEventListeners(Event.create(this, Type.JOB_FINISHED, node));
 							}
 						}
 						
 						updateFlow();
 					}
 					else {
-						if (isFlowFinished()) {
+						if (isFlowFinished() || flowCancelled ) {
 							flowFinished = true;
 							break;
 						}
@@ -339,6 +356,32 @@ public class FlowRunner extends EventHandler implements Runnable {
 			}
 		}
 		
+		if (flowCancelled) {
+			try {
+				logger.info("Flow was force cancelled cleaning up.");
+				for(JobRunner activeRunner : activeJobRunners.values()) {
+					activeRunner.cancel();
+				}
+				
+				for (ExecutableNode node: flow.getExecutableNodes()) {
+					if (Status.isStatusFinished(node.getStatus())) {
+						continue;
+					}
+					else if (node.getStatus() == Status.DISABLED) {
+						node.setStatus(Status.SKIPPED);
+					}
+					else {
+						node.setStatus(Status.KILLED);
+					}
+					fireEventListeners(Event.create(this, Type.JOB_FINISHED, node));
+				}
+			} catch (Exception e) {
+				logger.error(e);
+			}
+	
+			updateFlow();
+		}
+		
 		logger.info("Finishing up flow. Awaiting Termination");
 		executorService.shutdown();
 		
@@ -419,12 +462,10 @@ public class FlowRunner extends EventHandler implements Runnable {
 		ExecutionOptions options = flow.getExecutionOptions();
 		@SuppressWarnings("unchecked")
 		Props flowProps = new Props(null, options.getFlowParameters()); 
-		
-		if (flowProps.size() > 0) {
-			flowProps.setParent(parentProps);
-			parentProps = flowProps;
-		}
-		
+		flowProps.putAll(commonProps);
+		flowProps.setParent(parentProps);
+		parentProps = flowProps;
+
 		// We add the previous job output and put into this props.
 		if (previousOutput != null) {
 			Props earliestParent = previousOutput.getEarliestAncestor();
@@ -531,6 +572,7 @@ public class FlowRunner extends EventHandler implements Runnable {
 			if (watcher != null) {
 				logger.info("Watcher is attached. Stopping watcher.");
 				watcher.stopWatcher();
+				logger.info("Watcher cancelled status is " + watcher.isWatchCancelled());
 			}
 			
 			logger.info("Cancelling " + activeJobRunners.size() + " jobs.");
diff --git a/src/java/azkaban/execapp/FlowRunnerManager.java b/src/java/azkaban/execapp/FlowRunnerManager.java
index a293ac7..d707c1f 100644
--- a/src/java/azkaban/execapp/FlowRunnerManager.java
+++ b/src/java/azkaban/execapp/FlowRunnerManager.java
@@ -75,6 +75,7 @@ public class FlowRunnerManager implements EventListener {
 	private ExecutorService executorService;
 	private SubmitterThread submitterThread;
 	private CleanerThread cleanerThread;
+	private int numJobThreadPerFlow = 10;
 	
 	private ExecutorLoader executorLoader;
 	private ProjectLoader projectLoader;
@@ -119,6 +120,7 @@ public class FlowRunnerManager implements EventListener {
 		
 		//azkaban.temp.dir
 		numThreads = props.getInt("executor.flow.threads", DEFAULT_NUM_EXECUTING_FLOWS);
+		numJobThreadPerFlow = props.getInt("flow.num.job.threads", numJobThreadPerFlow);
 		executorService = Executors.newFixedThreadPool(numThreads);
 		
 		this.executorLoader = executorLoader;
@@ -388,11 +390,12 @@ public class FlowRunnerManager implements EventListener {
 		}
 
 		FlowRunner runner = new FlowRunner(flow, executorLoader, projectLoader, jobtypeManager);
-		runner.setFlowWatcher(watcher);
-		runner.setJobLogSettings(jobLogChunkSize, jobLogNumFiles);
-		runner.setValidateProxyUser(validateProxyUser);
-		runner.setGlobalProps(globalProps);
-		runner.addListener(this);
+		runner.setFlowWatcher(watcher)
+			.setJobLogSettings(jobLogChunkSize, jobLogNumFiles)
+			.setValidateProxyUser(validateProxyUser)
+			.setGlobalProps(globalProps)
+			.setNumJobThreads(numJobThreadPerFlow)
+			.addListener(this);
 		
 		// Check again.
 		if (runningFlows.containsKey(execId)) {
diff --git a/src/java/azkaban/execapp/JobRunner.java b/src/java/azkaban/execapp/JobRunner.java
index 40bc1ec..5f8cade 100644
--- a/src/java/azkaban/execapp/JobRunner.java
+++ b/src/java/azkaban/execapp/JobRunner.java
@@ -85,6 +85,7 @@ public class JobRunner extends EventHandler implements Runnable {
 
 	private long delayStartMs = 0;
 	private boolean cancelled = false;
+	private BlockingStatus currentBlockStatus = null;
 	
 	public JobRunner(ExecutableNode node, Props props, File workingDir, ExecutorLoader loader, JobTypeManager jobtypeManager) {
 		this.props = props;
@@ -223,19 +224,25 @@ public class JobRunner extends EventHandler implements Runnable {
 					
 					for(BlockingStatus bStatus: blockingStatus) {
 						logger.info("Waiting on pipelined job " + bStatus.getJobId());
+						currentBlockStatus = bStatus;
 						bStatus.blockOnFinishedStatus();
 						logger.info("Pipelined job " + bStatus.getJobId() + " finished.");
+						if (watcher.isWatchCancelled()) {
+							break;
+						}
 					}
 				}
 				if (watcher.isWatchCancelled()) {
 					logger.info("Job was cancelled while waiting on pipeline. Quiting.");
 					node.setStartTime(System.currentTimeMillis());
 					node.setEndTime(System.currentTimeMillis());
+					node.setStatus(Status.FAILED);
 					fireEvent(Event.create(this, Type.JOB_FINISHED));
 					return;
 				}
 			}
 			
+			currentBlockStatus = null;
 			long currentTime = System.currentTimeMillis();
 			if (delayStartMs > 0) {
 				logger.info("Delaying start of execution for " + delayStartMs + " milliseconds.");
@@ -390,6 +397,11 @@ public class JobRunner extends EventHandler implements Runnable {
 			logError("Cancel has been called.");
 			this.cancelled = true;
 			
+			BlockingStatus status = currentBlockStatus;
+			if (status != null) {
+				status.unblock();
+			}
+			
 			// Cancel code here
 			if (job == null) {
 				logError("Job hasn't started yet.");
diff --git a/src/java/azkaban/executor/ExecutableFlow.java b/src/java/azkaban/executor/ExecutableFlow.java
index 408ed6d..883a19f 100644
--- a/src/java/azkaban/executor/ExecutableFlow.java
+++ b/src/java/azkaban/executor/ExecutableFlow.java
@@ -94,12 +94,12 @@ public class ExecutableFlow {
 		return flowProps.values();
 	}
 	
-	public void setProxyUsers(HashSet<String> proxyUsers) {
-		this.proxyUsers = proxyUsers;
+	public void addAllProxyUsers(Collection<String> proxyUsers) {
+		this.proxyUsers.addAll(proxyUsers);
 	}
 	
-	public HashSet<String> getProxyUsers() {
-		return this.proxyUsers;
+	public Set<String> getProxyUsers() {
+		return new HashSet<String>(this.proxyUsers);
 	}
 	
 	public void setExecutionOptions(ExecutionOptions options) {
@@ -286,7 +286,6 @@ public class ExecutableFlow {
 		flowObj.put("nodes", nodes);
 		
 		ArrayList<String> proxyUserList = new ArrayList<String>(proxyUsers);
-		
 		flowObj.put("proxyUsers", proxyUserList);
 
 		return flowObj;
@@ -357,7 +356,6 @@ public class ExecutableFlow {
 		
 		this.flowStatus = Status.fromInteger((Integer)updateData.get("status"));
 		
-		System.out.println("Updating status to " + flowStatus);
 		this.startTime = JSONUtils.getLongFromObject(updateData.get("startTime"));
 		this.endTime = JSONUtils.getLongFromObject(updateData.get("endTime"));
 		this.updateTime = JSONUtils.getLongFromObject(updateData.get("updateTime"));
@@ -406,7 +404,7 @@ public class ExecutableFlow {
 		
 		if(flowObj.containsKey("proxyUsers")) {
 			ArrayList<String> proxyUserList = (ArrayList<String>) flowObj.get("proxyUsers");
-			exFlow.setProxyUsers(new HashSet<String>(proxyUserList));
+			exFlow.addAllProxyUsers(proxyUserList);
 		}
 		
 		return exFlow;
diff --git a/src/java/azkaban/executor/ExecutorManager.java b/src/java/azkaban/executor/ExecutorManager.java
index 3f482bc..07b81a8 100644
--- a/src/java/azkaban/executor/ExecutorManager.java
+++ b/src/java/azkaban/executor/ExecutorManager.java
@@ -79,7 +79,7 @@ public class ExecutorManager {
 		executingManager = new ExecutingManagerUpdaterThread();
 		executingManager.start();
 
-		long executionLogsRetentionMs = props.getLong("azkaban.execution.logs.retention.ms", DEFAULT_EXECUTION_LOGS_RETENTION_MS);
+		long executionLogsRetentionMs = props.getLong("execution.logs.retention.ms", DEFAULT_EXECUTION_LOGS_RETENTION_MS);
 		cleanerThread = new CleanerThread(executionLogsRetentionMs);
 		cleanerThread.start();
 	}
diff --git a/src/java/azkaban/jobtype/JobTypeManager.java b/src/java/azkaban/jobtype/JobTypeManager.java
index dc24db7..3bfc51d 100644
--- a/src/java/azkaban/jobtype/JobTypeManager.java
+++ b/src/java/azkaban/jobtype/JobTypeManager.java
@@ -81,7 +81,7 @@ public class JobTypeManager
 	private void loadDefaultTypes() throws JobTypeManagerException{
 		jobToClass.put("command", ProcessJob.class);
 		jobToClass.put("javaprocess", JavaProcessJob.class);
-		jobToClass.put("propertyPusher", NoopJob.class);
+		jobToClass.put("noop", NoopJob.class);
 		jobToClass.put("python", PythonJob.class);
 		jobToClass.put("ruby", RubyJob.class);
 		jobToClass.put("script", ScriptJob.class);	
diff --git a/src/java/azkaban/project/Project.java b/src/java/azkaban/project/Project.java
index 7344820..d2e1f52 100644
--- a/src/java/azkaban/project/Project.java
+++ b/src/java/azkaban/project/Project.java
@@ -17,11 +17,13 @@
 package azkaban.project;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import azkaban.flow.Flow;
 import azkaban.user.Permission;
@@ -44,14 +46,6 @@ public class Project {
 	private Map<String, Flow> flows = null;
 	private HashSet<String> proxyUsers = new HashSet<String>();
 	
-	public HashSet<String> getProxyUsers() {
-		return proxyUsers;
-	}
-	
-	public void setProxyUsers(HashSet<String> proxyUsers) {
-		this.proxyUsers = proxyUsers;
-	}
-	
 	public Project(int id, String name) {
 		this.id = id;
 		this.name = name;
@@ -101,6 +95,26 @@ public class Project {
 		return permissions;
 	}
 	
+	public Set<String> getProxyUsers() {
+		return new HashSet<String>(proxyUsers);
+	}
+	
+	public void addAllProxyUsers(Collection<String> proxyUsers) {
+		this.proxyUsers.addAll(proxyUsers);
+	}
+	
+	public boolean hasProxyUser(String proxy) {
+		return this.proxyUsers.contains(proxy);
+	}
+	
+	public void addProxyUser(String user) {
+		this.proxyUsers.add(user);
+	}
+	
+	public void removeProxyUser(String user) {
+		this.proxyUsers.remove(user);
+	}
+	
 	public boolean hasPermission(User user, Type type) {
 		Permission perm = userPermissionMap.get(user.getUserId());
 		if (perm != null && (perm.isPermissionSet(Type.ADMIN) || perm.isPermissionSet(type))) {
@@ -294,8 +308,7 @@ public class Project {
 		}
 		
 		List<String> proxyUserList = (List<String>) projectObject.get("proxyUsers");
-		HashSet<String> proxyUsers = new HashSet<String>(proxyUserList);
-		project.setProxyUsers(proxyUsers);
+		project.addAllProxyUsers(proxyUserList);
 
 		return project;
 	}
@@ -409,27 +422,4 @@ public class Project {
 	public void setVersion(int version) {
 		this.version = version;
 	}
-
-	public List<String> getProxyUserList() {
-		return new ArrayList<String>(proxyUsers);
-	}
-
-//	public Object getSettingsObject() {
-//		HashMap<String, Object> projectObject = new HashMap<String, Object>();
-//		ArrayList<String> proxyUserList = new ArrayList<String>(proxyUsers);
-//		projectObject.put("proxyUsers", proxyUserList);
-//		
-//		return projectObject;
-//	}
-//	
-//	@SuppressWarnings("unchecked")
-//	public static HashSet<String> proxyUserFromSettingsObj(Object object) {
-//		Map<String, Object> settingsObj = (Map<String, Object>) object;
-//
-//		List<String> proxyUserList = (List<String>) settingsObj.get("proxyUsers");
-//		HashSet<String> proxyUsers = new HashSet<String>(proxyUserList);
-//		
-//		return proxyUsers;
-//	}
-
 }
diff --git a/src/java/azkaban/project/ProjectLogEvent.java b/src/java/azkaban/project/ProjectLogEvent.java
index c6e8577..af38a65 100644
--- a/src/java/azkaban/project/ProjectLogEvent.java
+++ b/src/java/azkaban/project/ProjectLogEvent.java
@@ -7,7 +7,16 @@ public class ProjectLogEvent {
 	 * Only represent from 0 to 255 different codes.
 	 */
 	public static enum EventType {
-		ERROR(128), CREATED(1), DELETED(2), USER_PERMISSION(3), GROUP_PERMISSION(4), DESCRIPTION(5), UPLOADED(6), SCHEDULE(7), SLA(8);
+		ERROR(128), 
+		CREATED(1),
+		DELETED(2), 
+		USER_PERMISSION(3), 
+		GROUP_PERMISSION(4), 
+		DESCRIPTION(5), 
+		UPLOADED(6), 
+		SCHEDULE(7), 
+		SLA(8), 
+		PROXY_USER(9);
 
 		private int numVal;
 
@@ -37,6 +46,8 @@ public class ProjectLogEvent {
 				return SCHEDULE;
 			case 8:
 				return SLA;
+			case 9:
+				return PROXY_USER;
 			case 128:
 				return ERROR;
 			default:
diff --git a/src/java/azkaban/project/ProjectManager.java b/src/java/azkaban/project/ProjectManager.java
index a5be28b..f610e40 100644
--- a/src/java/azkaban/project/ProjectManager.java
+++ b/src/java/azkaban/project/ProjectManager.java
@@ -41,7 +41,7 @@ public class ProjectManager {
 		this.projectVersionRetention = (props.getInt("project.version.retention", 3));
 		logger.info("Project version retention is set to " + projectVersionRetention);
 		
-		this.creatorDefaultPermissions = props.getBoolean("azkaban.creator.default.permissions", true);
+		this.creatorDefaultPermissions = props.getBoolean("creator.default.proxy", true);
 		
 		if (!tempDir.exists()) {
 			tempDir.mkdirs();
@@ -235,6 +235,22 @@ public class ProjectManager {
 		projectLoader.updateProjectSettings(project);		
 	}
 	
+	public void addProjectProxyUser(Project project, String proxyName, User modifier) throws ProjectManagerException {
+		logger.info("User " + modifier.getUserId() + " adding proxy user " + proxyName + " to project " + project.getName());
+		project.addProxyUser(proxyName);
+		
+		projectLoader.postEvent(project, EventType.PROXY_USER, modifier.getUserId(), "Proxy user " + proxyName + " is added to project.");
+		updateProjectSetting(project);
+	}
+	
+	public void removeProjectProxyUser(Project project, String proxyName, User modifier) throws ProjectManagerException {
+		logger.info("User " + modifier.getUserId() + " removing proxy user " + proxyName + " from project " + project.getName());
+		project.removeProxyUser(proxyName);
+		
+		projectLoader.postEvent(project, EventType.PROXY_USER, modifier.getUserId(), "Proxy user " + proxyName + " has been removed form the project.");
+		updateProjectSetting(project);
+	}
+	
 	public void updateProjectPermission(Project project, String name, Permission perm, boolean group, User modifier) throws ProjectManagerException {
 		logger.info("User " + modifier.getUserId() + " updating permissions for project " + project.getName() + " for " + name + " " + perm.toString());
 		projectLoader.updatePermission(project, name, perm, group);
diff --git a/src/java/azkaban/scheduler/ScheduleManager.java b/src/java/azkaban/scheduler/ScheduleManager.java
index fab237a..5688f66 100644
--- a/src/java/azkaban/scheduler/ScheduleManager.java
+++ b/src/java/azkaban/scheduler/ScheduleManager.java
@@ -371,7 +371,7 @@ public class ScheduleManager {
 									// Create ExecutableFlow
 									ExecutableFlow exflow = new ExecutableFlow(flow);
 									exflow.setSubmitUser(runningSched.getSubmitUser());
-									exflow.setProxyUsers(project.getProxyUsers());
+									exflow.addAllProxyUsers(project.getProxyUsers());
 									
 									ExecutionOptions flowOptions = runningSched.getExecutionOptions();
 									if(flowOptions == null) {
diff --git a/src/java/azkaban/user/XmlUserManager.java b/src/java/azkaban/user/XmlUserManager.java
index 042a081..55ee224 100644
--- a/src/java/azkaban/user/XmlUserManager.java
+++ b/src/java/azkaban/user/XmlUserManager.java
@@ -66,7 +66,7 @@ public class XmlUserManager implements UserManager {
 	private HashMap<String, String> userPassword;
 	private HashMap<String, Role> roles;
 	private HashMap<String, Set<String>> groupRoles;
-	private HashMap<String, HashSet<String>> proxyUserMap;
+	private HashMap<String, Set<String>> proxyUserMap;
 
 	/**
 	 * The constructor.
@@ -89,7 +89,7 @@ public class XmlUserManager implements UserManager {
 		HashMap<String, String> userPassword = new HashMap<String, String>();
 		HashMap<String, Role> roles = new HashMap<String, Role>();
 		HashMap<String, Set<String>> groupRoles = new HashMap<String, Set<String>>();
-		HashMap<String, HashSet<String>> proxyUserMap = new HashMap<String, HashSet<String>>();
+		HashMap<String, Set<String>> proxyUserMap = new HashMap<String, Set<String>>();
 		
 		// Creating the document builder to parse xml.
 		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
@@ -143,7 +143,7 @@ public class XmlUserManager implements UserManager {
 		}
 	}
 
-	private void parseUserTag(Node node, HashMap<String, User> users, HashMap<String, String> userPassword, HashMap<String, HashSet<String>> proxyUserMap) {
+	private void parseUserTag(Node node, HashMap<String, User> users, HashMap<String, String> userPassword, HashMap<String, Set<String>> proxyUserMap) {
 		NamedNodeMap userAttrMap = node.getAttributes();
 		Node userNameAttr = userAttrMap.getNamedItem(USERNAME_ATTR);
 		if (userNameAttr == null) {
@@ -175,16 +175,15 @@ public class XmlUserManager implements UserManager {
 		Node proxy = userAttrMap.getNamedItem(PROXY_ATTR);
 		if (proxy != null) {
 			String value = proxy.getNodeValue();
-			String[] groupSplit = value.split("\\s*,\\s*");
-			for (String group : groupSplit) {
-				if(proxyUserMap.containsKey(username)) {
-					proxyUserMap.get(username).add(group);
-				}
-				else {
-					HashSet<String> proxySet = new HashSet<String>();
-					proxySet.add(group);
+			String[] proxySplit = value.split("\\s*,\\s*");
+			for (String proxyUser : proxySplit) {
+				Set<String> proxySet = proxyUserMap.get(username);
+				if (proxySet == null) {
+					proxySet = new HashSet<String>();
 					proxyUserMap.put(username, proxySet);
 				}
+				
+				proxySet.add(proxyUser);
 			}
 		}
 
diff --git a/src/java/azkaban/webapp/AzkabanWebServer.java b/src/java/azkaban/webapp/AzkabanWebServer.java
index 5ce6985..e3fe50f 100644
--- a/src/java/azkaban/webapp/AzkabanWebServer.java
+++ b/src/java/azkaban/webapp/AzkabanWebServer.java
@@ -448,7 +448,6 @@ public class AzkabanWebServer implements AzkabanServer {
 		//root.addServlet(new ServletHolder(new HdfsBrowserServlet()), "/hdfs/*");
 		
 		root.setAttribute(AzkabanServletContextListener.AZKABAN_SERVLET_CONTEXT_KEY, app);
-		
 		try {
 			server.start();
 		} 
diff --git a/src/java/azkaban/webapp/servlet/ExecutorServlet.java b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
index a9d5d17..ea19307 100644
--- a/src/java/azkaban/webapp/servlet/ExecutorServlet.java
+++ b/src/java/azkaban/webapp/servlet/ExecutorServlet.java
@@ -763,7 +763,7 @@ public class ExecutorServlet extends LoginAbstractAzkabanServlet {
 		
 		ExecutableFlow exflow = new ExecutableFlow(flow);
 		exflow.setSubmitUser(user.getUserId());
-		exflow.setProxyUsers(project.getProxyUsers());
+		exflow.addAllProxyUsers(project.getProxyUsers());
 
 		ExecutionOptions options = HttpRequestUtils.parseFlowOptions(req);
 		exflow.setExecutionOptions(options);
diff --git a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 455e441..0b2d413 100644
--- a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -28,7 +28,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -235,6 +234,11 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 				ajaxAddProxyUser(project, ret, req, user);
 			}
 		}
+		else if (ajaxName.equals("removeProxyUser")) {
+			if (handleAjaxPermission(project, user, Type.ADMIN, ret)) {
+				ajaxRemoveProxyUser(project, ret, req, user);
+			}
+		}
 		else if (ajaxName.equals("fetchFlowExecutions")) {
 			if (handleAjaxPermission(project, user, Type.READ, ret)) {
 				ajaxFetchFlowExecutions(project, ret, req);
@@ -569,53 +573,34 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		ret.put("nodes", nodeList);
 	}
 	
+	
 	private void ajaxAddProxyUser(Project project, HashMap<String, Object> ret, HttpServletRequest req, User user) throws ServletException {
 		String name = getParam(req, "name");
 		
 		logger.info("Adding proxy user " + name + " by " + user.getUserId());
-		
-
-		
-		boolean doProxy = Boolean.parseBoolean(getParam(req, "doProxy"));
-		
-		
-		HashSet<String> proxyUsers = project.getProxyUsers();
-		//add
-		if(doProxy) {			
-			if(proxyUsers.contains(name)) {
-				return;
-			}
-			else {
-				if(userManager.validateProxyUser(name, user)) {
-					proxyUsers.add(name);
-				}
-				else {
-					ret.put("error", "User " + user.getUserId() + " has no permission to add " + name + " as proxy user for project " + project.getName());
-					return;
-				}
+		if(userManager.validateProxyUser(name, user)) {
+			try {
+				projectManager.addProjectProxyUser(project, name, user);
+			} catch (ProjectManagerException e) {
+				ret.put("error", e.getMessage());
 			}
 		}
 		else {
-			if(!proxyUsers.contains(name)) {
-				return;
-			}
-			else {
-				if(userManager.validateProxyUser(name, user)) {
-					proxyUsers.remove(name);
-				}
-				else {
-					ret.put("error", "User " + user.getUserId() + " has no permission to remove " + name + " as proxy user for project " + project.getName());
-					return;
-				}
-			}
+			ret.put("error", "User " + user.getUserId() + " has no permission to add " + name + " as proxy user.");
+			return;
 		}
+	}
+	
+	private void ajaxRemoveProxyUser(Project project, HashMap<String, Object> ret, HttpServletRequest req, User user) throws ServletException {
+		String name = getParam(req, "name");
+		
+		logger.info("Removing proxy user " + name + " by " + user.getUserId());
+
 		try {
-			projectManager.updateProjectSetting(project);
+			projectManager.removeProjectProxyUser(project, name, user);
 		} catch (ProjectManagerException e) {
-			// TODO Auto-generated catch block
 			ret.put("error", e.getMessage());
 		}
-		
 	}
 	
 	private void ajaxAddPermission(Project project, HashMap<String, Object> ret, HttpServletRequest req, User user) throws ServletException {
@@ -862,9 +847,20 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 					page.add("admin", true);
 				}
 
-				page.add("permissions", project.getUserPermissions());
-				page.add("groupPermissions", project.getGroupPermissions());
-				page.add("proxyUsers", project.getProxyUserList());
+				List<Pair<String, Permission>> userPermission = project.getUserPermissions();
+				if (userPermission != null && !userPermission.isEmpty()) {
+					page.add("permissions", userPermission);
+				}
+				
+				List<Pair<String, Permission>> groupPermission = project.getGroupPermissions();
+				if (groupPermission != null && !groupPermission.isEmpty()) {
+					page.add("groupPermissions", groupPermission);
+				}
+				
+				Set<String> proxyUsers = project.getProxyUsers();
+				if (proxyUsers != null && !proxyUsers.isEmpty()) {
+					page.add("proxyUsers", proxyUsers);
+				}
 				
 				if(hasPermission(project, user, Type.ADMIN)) {
 					page.add("isAdmin", true);
@@ -1174,6 +1170,9 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 				if (perm.isPermissionSet(Type.EXECUTE) || adminPerm) {
 					page.add("exec", true);
 				}
+				else {
+					page.add("exec", false);
+				}
 				
 				List<Flow> flows = project.getFlows();
 				if (!flows.isEmpty()) {
diff --git a/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm b/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
index 046d1c0..3d73924 100644
--- a/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
@@ -103,8 +103,8 @@
 			<tbody>
 #if($permissions)
 #foreach($perm in $permissions)
-	<tr id="${perm.first}-row" >
-		<td class="tb-name">#if($perm.first == $username) ${perm.first} <span class="sublabel">(you)</span> #else $perm.first #end</td>
+	<tr>
+		<td class="tb-username">#if($perm.first == $username) ${perm.first} <span class="sublabel">(you)</span> #else $perm.first #end</td>
 		#if ($perm.second.isPermissionNameSet("ADMIN")) 
 			<td><input id="${perm.first}-admin-checkbox" type="checkbox" name="admin" disabled="disabled" checked="true"></input></td>
 			<td><input id="${perm.first}-read-checkbox" type="checkbox" name="read" disabled="disabled" checked="true"></input></td>
@@ -147,8 +147,8 @@
 			<tbody>
 #if($groupPermissions)
 #foreach($perm in $groupPermissions)
-	<tr id="${perm.first}-row" >
-		<td class="tb-name">#if($perm.first == $username) ${perm.first} <span class="sublabel">(you)</span> #else $perm.first #end</td>
+	<tr>
+		<td class="tb-username">#if($perm.first == $username) ${perm.first} <span class="sublabel">(you)</span> #else $perm.first #end</td>
 		#if ($perm.second.isPermissionNameSet("ADMIN")) 
 			<td><input id="group-${perm.first}-admin-checkbox" type="checkbox" name="admin" disabled="disabled" checked="true"></input></td>
 			<td><input id="group-${perm.first}-read-checkbox" type="checkbox" name="read" disabled="disabled" checked="true"></input></td>
@@ -174,17 +174,11 @@
 			</tbody>
 		</table>
 		
-		<br></br>
+		<br/>
 		<table id="proxy-user-table" class="all-jobs permission-table">
 			<thead>
 				<tr>
 					<th class="tb-username">Proxy User</th>
-					<th class="tb-perm">Admin</th>
-					<th class="tb-read">Read</th>
-					<th class="tb-write">Write</th>
-					<th class="tb-execute">Execute</th>
-					<th class="tb-schedule">Schedule</th>
-					<th class="tb-proxy">Proxy</th>
 					#if($isAdmin)
 						<th class="tb-action"></th>
 					#end
@@ -193,17 +187,10 @@
 			<tbody>
 #if($proxyUsers)
 #foreach($proxyUser in $proxyUsers)
-	<tr id="${proxyUser}-row" >
-		<td class="tb-name">#if($proxyUser == $username) ${proxyUser} <span class="sublabel">(you)</span> #else $proxyUser #end</td>
-			<td><input id="proxy-${proxyUser}-admin-checkbox" type="checkbox" name="admin" disabled="disabled" ></input></td>
-			<td><input id="proxy-${proxyUser}-read-checkbox" type="checkbox" name="read" disabled="disabled" ></input></td>
-			<td><input id="proxy-${proxyUser}-write-checkbox" type="checkbox" name="write" disabled="disabled" ></input></td>
-			<td><input id="proxy-${proxyUser}-execute-checkbox" type="checkbox" name="execute" disabled="disabled" ></input></td>
-			<td><input id="proxy-${proxyUser}-schedule-checkbox" type="checkbox" name="schedule" disabled="disabled" ></input></td>
-			<td><input id="proxy-${proxyUser}-proxy-checkbox" type="checkbox" name="proxy" disabled="disabled" checked="true"></input></td>
-
+	<tr>
+		<td class="tb-username">#if($proxyUser == $username) ${proxyUser} <span class="sublabel">(you)</span> #else $proxyUser #end</td>
 		#if($isAdmin)
-			<td><button id="proxy-${proxyUser}" class="change-btn btn2">Change</button></td>
+			<td><button id="proxy-${proxyUser}" name="${proxyUser}" class="remove-btn btn2">Remove</button></td>
 		#end
 	</tr>
 #end
@@ -216,6 +203,29 @@
 
 		</div>
 	
+		<div id="remove-proxy" class="modal">
+			<h3>Remove Proxy User</h3>
+			<div id="removeProxyErrorMsg" class="box-error-message"></div>
+			<p id="proxyRemoveMsg">Removing Proxy User </p>
+			<div class="actions">
+				<a class="yes btn2" id="remove-proxy-btn" href="#">Remove Proxy User</a>
+				<a class="no simplemodal-close btn3" href="#">Cancel</a>
+			</div>
+		</div>
+	
+		<div id="add-proxy" class="modal">
+			<h3 id="proxy-title">Add Proxy User</h3>
+			<div id="proxyErrorMsg" class="box-error-message"></div>
+			<dl>
+				<dt>Proxy</dt>
+				<dd><input id="proxy-user-box" name="proxyid" type="text" /></dd>
+			</dl>
+			<div class="actions">
+				<a class="yes btn2" id="add-proxy-btn" href="#">Add Proxy User</a>
+				<a class="no simplemodal-close btn3" href="#">Cancel</a>
+			</div>
+		</div>
+	
 		<div id="change-permission" class="modal">
 			<h3 id="change-title">Change Permissions</h3>
 			<div id="errorMsg" class="box-error-message"></div>
@@ -236,10 +246,6 @@
 					    <dt>Schedule</dt>
 					    <dd><input id="schedule-change" name="schedule" type="checkbox" /></dd>
 					</div>
-					<div id="proxyCheckBox" hidden=true>
-				    	<dt>Proxy</dt>
-				    	<dd><input id="proxy-change" name="proxy" type="checkbox" /></dd>
-				    </div>
 					</dl>
 				</fieldset>
 			</div>
@@ -247,12 +253,12 @@
 				<a class="yes btn2" id="change-btn" href="#">Commit</a>
 				<a class="no simplemodal-close btn3" href="#">Cancel</a>
 			</div>
-			<div id="invalid-session" class="modal">
-				<h3>Invalid Session</h3>
-				<p>Session has expired. Please re-login.</p>
-				<div class="actions">
-					<a class="yes btn2" id="login-btn" href="#">Re-login</a>
-				</div>
+		</div>
+		<div id="invalid-session" class="modal">
+			<h3>Invalid Session</h3>
+			<p>Session has expired. Please re-login.</p>
+			<div class="actions">
+				<a class="yes btn2" id="login-btn" href="#">Re-login</a>
 			</div>
 		</div>
 	</body>
diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index b187638..aadf527 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -735,10 +735,22 @@ tr:hover td {
 
 #user-box {
 	background-color: #FFF;
-	border-width: 1px;
-	border-color: #CCC;
+	margin-left: 10px;
 	width: 300px;
+	border-style: solid;
+	border-color: #CCC;
+	height: 20px;
+	font-size: 11pt;
+}
+
+#proxy-user-box {
+	background-color: #FFF;
 	margin-left: 10px;
+	width: 300px;
+	border-style: solid;
+	border-color: #CCC;
+	height: 20px;
+	font-size: 11pt;
 }
 
 #create-project #overwrite {
@@ -2065,31 +2077,32 @@ table.parameters tr td {
 }
 
 .permission-table .tb-username {
-	margin: 0px;
+	text-align:left;
+	padding-left: 10px;
 }
 
 .permission-table .tb-perm {
-	width: 60px;
+	width: 41px;
 	margin: 0px;
 }
 
 .permission-table .tb-admin {
-	width: 60px;
+	width: 41px;
 	margin: 0px;
 }
 
 .permission-table .tb-read {
-	width: 60px;
+	width: 33px;
 	margin: 0px;
 }
 
 .permission-table .tb-write {
-	width: 60px;
+	width: 34px;
 	margin: 0px;
 }
 
 .permission-table .tb-execute {
-	width: 60px;
+	width: 51px;
 	margin: 0px;
 }
 
@@ -2100,7 +2113,9 @@ table.parameters tr td {
 
 .permission-table .tb-action {
 	margin: 0px;
-	width: 100px;
+	width: 70px;
+	min-width: 70px;
+	max-width: 70px;
 }
 
 .permission-table thead tr th.tb-username {
diff --git a/src/web/js/azkaban.permission.view.js b/src/web/js/azkaban.permission.view.js
index 869d1f3..67c826b 100644
--- a/src/web/js/azkaban.permission.view.js
+++ b/src/web/js/azkaban.permission.view.js
@@ -2,7 +2,7 @@ $.namespace('azkaban');
 
 var permissionTableView;
 var groupPermissionTableView;
-var proxyTableView;
+
 azkaban.PermissionTableView= Backbone.View.extend({
   events : {
 	"click button": "handleChangePermission"
@@ -19,6 +19,113 @@ azkaban.PermissionTableView= Backbone.View.extend({
   }
 });
 
+var proxyTableView;
+azkaban.ProxyTableView= Backbone.View.extend({
+  events : {
+	"click button": "handleRemoveProxy"
+  },
+  initialize : function(settings) {
+  },
+  render: function() {
+  },
+  handleRemoveProxy: function(evt) {
+	removeProxyView.display($(evt.currentTarget).attr("name"));
+  }
+});
+
+var removeProxyView;
+azkaban.RemoveProxyView = Backbone.View.extend({
+	events: {
+		"click #remove-proxy-btn": "handleRemoveProxy"
+	},
+	initialize : function(settings) {
+		$('#removeProxyErrorMsg').hide();
+	},
+	display: function(proxyName) {
+		this.el.proxyName = proxyName;
+		$("#proxyRemoveMsg").text("Removing proxy user '" + proxyName + "'");
+	  	 $(this.el).modal({
+	          closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
+	          position: ["20%",],
+	          containerId: 'confirm-container',
+	          containerCss: {
+	            'height': '220px',
+	            'width': '565px'
+	          },
+	          onShow: function (dialog) {
+	            var modal = this;
+	            $("#removeProxyErrorMsg").hide();
+	          }
+        });
+	},
+	handleRemoveProxy: function() {
+	  	var requestURL = contextURL + "/manager";
+		var proxyName = this.el.proxyName;
+
+	  	$.get(
+	  	      requestURL,
+	  	      {"project": projectName, "name": proxyName, "ajax":"removeProxyUser"},
+	  	      function(data) {
+	  	      	  console.log("Output");
+	  	      	  if (data.error) {
+	  	      	  	$("#removeProxyErrorMsg").text(data.error);
+	  	      	  	$("#removeProxyErrorMsg").show();
+	  	      	  	return;
+	  	      	  }
+	  	      	  
+	  	      	  var replaceURL = requestURL + "?project=" + projectName +"&permissions";
+	  	          window.location.replace(replaceURL);
+	  	      },
+	  	      "json"
+	  	    );
+	}
+});
+
+var addProxyView;
+azkaban.AddProxyView = Backbone.View.extend({
+	events: {
+		"click #add-proxy-btn": "handleAddProxy"
+	},
+	initialize : function(settings) {
+		$('#proxyErrorMsg').hide();
+	},
+	display: function() {
+	  	 $(this.el).modal({
+	          closeHTML: "<a href='#' title='Close' class='modal-close'>x</a>",
+	          position: ["20%",],
+	          containerId: 'confirm-container',
+	          containerCss: {
+	            'height': '220px',
+	            'width': '565px'
+	          },
+	          onShow: function (dialog) {
+	            var modal = this;
+	            $("#errorMsg").hide();
+	          }
+        });
+	},
+	handleAddProxy: function() {
+	  	var requestURL = contextURL + "/manager";
+	  	var name = $('#proxy-user-box').val();
+		
+	  	$.get(
+	  	      requestURL,
+	  	      {"project": projectName, "name": name, "ajax":"addProxyUser"},
+	  	      function(data) {
+	  	      	  console.log("Output");
+	  	      	  if (data.error) {
+	  	      	  	$("#proxyErrorMsg").text(data.error);
+	  	      	  	$("#proxyErrorMsg").show();
+	  	      	  	return;
+	  	      	  }
+	  	      	  
+	  	      	  var replaceURL = requestURL + "?project=" + projectName +"&permissions";
+	  	          window.location.replace(replaceURL);
+	  	      },
+	  	      "json"
+	  	    );
+	}
+});
 
 var changePermissionView;
 azkaban.ChangePermissionView= Backbone.View.extend({
@@ -44,7 +151,6 @@ azkaban.ChangePermissionView= Backbone.View.extend({
 	$('#user-box').val(this.userid);
 	this.newPerm = newPerm;
 	this.group = group;
-	this.proxy = proxy;
 	
 	var prefix = userid;
 	var adminInput = $("#" + prefix + "-admin-checkbox");
@@ -52,7 +158,6 @@ azkaban.ChangePermissionView= Backbone.View.extend({
 	var writeInput = $("#" + prefix + "-write-checkbox");
 	var executeInput = $("#" + prefix + "-execute-checkbox");
 	var scheduleInput = $("#" + prefix + "-schedule-checkbox");
-	var proxyInput = $("#" + prefix + "-proxy-checkbox");
 	
 	if (newPerm) {
 		if (group) {
@@ -72,39 +177,22 @@ azkaban.ChangePermissionView= Backbone.View.extend({
 		this.permission.write = false;
 		this.permission.execute = false;
 		this.permission.schedule = false;
-		this.doProxy = false;
-		
 	}
 	else {
 		if (group) {
 			$('#change-title').text("Change Group Permissions");
 		}
-		else if(proxy){
-			$('#change-title').text("Change Proxy User Permissions");
-			this.doProxy = $(proxyInput).attr("checked");
-		}
 		else {
 			$('#change-title').text("Change User Permissions");
 		}
 		
 		$('#user-box').attr("disabled", "disabled");
 		
-		
-		
-		this.permission.admin = $(adminInput).attr("checked");
-		this.permission.read = $(readInput).attr("checked");
-		this.permission.write = $(writeInput).attr("checked");
-		this.permission.execute = $(executeInput).attr("checked");
-		this.permission.schedule = $(scheduleInput).attr("checked");
-		this.doProxy = $(proxyInput).attr("checked");
-	}
-	
-	if(proxy) {
-		document.getElementById("otherCheckBoxes").hidden=true;
-		document.getElementById("proxyCheckBox").hidden=false;
-	} else {
-		document.getElementById("otherCheckBoxes").hidden=false;
-		document.getElementById("proxyCheckBox").hidden=true;
+		this.permission.admin = $(adminInput).is(":checked");
+		this.permission.read = $(readInput).is(":checked");
+		this.permission.write = $(writeInput).is(":checked");
+		this.permission.execute = $(executeInput).is(":checked");
+		this.permission.schedule = $(scheduleInput).is(":checked");
 	}
 	
 	this.changeCheckbox();
@@ -123,9 +211,6 @@ azkaban.ChangePermissionView= Backbone.View.extend({
             $("#errorMsg").hide();
           }
         });
-  	 
-
-  	 
   },
   render: function() {
   },
@@ -142,8 +227,6 @@ azkaban.ChangePermissionView= Backbone.View.extend({
   },
   changeCheckbox : function(evt) {
     var perm = this.permission;
-    var proxy = this.proxy;
-    var doProxy = this.doProxy;
 
   	if (perm.admin) {
   		$("#admin-change").attr("checked", true);
@@ -158,9 +241,6 @@ azkaban.ChangePermissionView= Backbone.View.extend({
   		
   		$("#schedule-change").attr("checked", true);
   		$("#schedule-change").attr("disabled", "disabled");
-  		
-  		$("#proxy-change").attr("checked", false);
-		$("#proxy-change").attr("disabled", "disabled");
   	}
   	else {
   		$("#admin-change").attr("checked", false);
@@ -176,16 +256,12 @@ azkaban.ChangePermissionView= Backbone.View.extend({
   		
   		$("#schedule-change").attr("checked", perm.schedule);
 		$("#schedule-change").attr("disabled", null);
-		
-		$("#proxy-change").attr("checked", doProxy);
-		$("#proxy-change").attr("disabled", null);
-		
   	}
   	
   	$("#change-btn").removeClass("btn-disabled");
   	$("#change-btn").attr("disabled", null);
   	
-  	if (perm.admin || perm.read || perm.write || perm.execute || perm.schedule || doProxy) {
+  	if (perm.admin || perm.read || perm.write || perm.execute || perm.schedule) {
   		$("#change-btn").text("Commit");
   	}
   	else {
@@ -202,14 +278,18 @@ azkaban.ChangePermissionView= Backbone.View.extend({
   	var requestURL = contextURL + "/manager";
   	var name = $('#user-box').val();
 	var command = this.newPerm ? "addPermission" : "changePermission";
-	if(this.proxy) {
-		command = "addProxyUser";
-	}
 	var group = this.group;
 	
+	var permission = {};
+	permission.admin = $("#admin-change").is(":checked");
+	permission.read = $("#read-change").is(":checked");
+	permission.write = $("#write-change").is(":checked");
+	permission.execute = $("#execute-change").is(":checked");
+	permission.schedule = $("#schedule-change").is(":checked");
+	
   	$.get(
 	      requestURL,
-	      {"project": projectName, "name": name, "ajax":command, "permissions": this.permission, "doProxy": this.doProxy, "group": group},
+	      {"project": projectName, "name": name, "ajax":command, "permissions": this.permission, "group": group},
 	      function(data) {
 	      	  console.log("Output");
 	      	  if (data.error) {
@@ -229,9 +309,10 @@ azkaban.ChangePermissionView= Backbone.View.extend({
 $(function() {
 	permissionTableView = new azkaban.PermissionTableView({el:$('#permissions-table'), group: false, proxy: false});
 	groupPermissionTableView = new azkaban.PermissionTableView({el:$('#group-permissions-table'), group: true, proxy: false});
-	proxyTableView = new azkaban.PermissionTableView({el:$('#proxy-user-table'), group: false, proxy: true});
+	proxyTableView = new azkaban.ProxyTableView({el:$('#proxy-user-table'), group: false, proxy: true});
 	changePermissionView = new azkaban.ChangePermissionView({el:$('#change-permission')});
-	
+	addProxyView = new azkaban.AddProxyView({el:$('#add-proxy')});
+	removeProxyView = new azkaban.RemoveProxyView({el:$('#remove-proxy')});
 	$('#addUser').bind('click', function() {
 		changePermissionView.display("", true, false, false);
 	});
@@ -241,7 +322,7 @@ $(function() {
 	});
 	
 	$('#addProxyUser').bind('click', function() {
-		changePermissionView.display("", true, false, true);
+		addProxyView.display();
 	});
 	
 });