|
@@ -0,0 +1,281 @@
|
|
|
+/**
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
+ * distributed with this work for additional information
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
+ * to you under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance
|
|
|
+ * with the License. You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+
|
|
|
+import Ember from 'ember';
|
|
|
+import Constants from 'yarn-ui/constants';
|
|
|
+
|
|
|
+export default Ember.Controller.extend({
|
|
|
+ queryParams: ["service", "attempt", "containerid"],
|
|
|
+ service: undefined,
|
|
|
+ attempt: undefined,
|
|
|
+ containerid: undefined,
|
|
|
+
|
|
|
+ selectedAttemptId: "",
|
|
|
+ attemptContainerList: null,
|
|
|
+ selectedContainerId: "",
|
|
|
+ selectedLogFileName: "",
|
|
|
+ containerLogFiles: null,
|
|
|
+ selectedContainerThreaddumpContent: "",
|
|
|
+ containerNodeMappings: [],
|
|
|
+ threaddumpErrorMessage: "",
|
|
|
+ stdoutLogsFetchFailedError: "",
|
|
|
+ appAttemptToStateMappings: [],
|
|
|
+
|
|
|
+ _isLoadingTopPanel: false,
|
|
|
+ _isLoadingBottomPanel: false,
|
|
|
+ _isThreaddumpAttemptFailed: false,
|
|
|
+ _isStdoutLogsFetchFailed: false,
|
|
|
+
|
|
|
+ initializeSelect: function(selector = ".js-fetch-attempt-containers") {
|
|
|
+ Ember.run.schedule("afterRender", this, function() {
|
|
|
+ $(selector).select2({ width: "350px", multiple: false });
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ actions: {
|
|
|
+ showContainersForAttemptId(attemptId, containerId = "") {
|
|
|
+ this.set("selectedAttemptId", "");
|
|
|
+ if (attemptId) {
|
|
|
+ this.set("_isLoadingTopPanel", true);
|
|
|
+ this.set("selectedAttemptId", attemptId);
|
|
|
+ this.fetchRunningContainersForAttemptId(attemptId)
|
|
|
+ .then(hash => {
|
|
|
+ let containers = null;
|
|
|
+ let containerIdArr = {};
|
|
|
+ var containerNodeMapping = null;
|
|
|
+ var nodeHttpAddress = null;
|
|
|
+
|
|
|
+ // Getting RUNNING containers from the RM first.
|
|
|
+ if (
|
|
|
+ hash.rmContainers.get("length") > 0 &&
|
|
|
+ hash.rmContainers.get("content")
|
|
|
+ ) {
|
|
|
+ // Create a container-to-NMnode mapping for later use.
|
|
|
+ hash.rmContainers.get("content").forEach(
|
|
|
+ function(containerInfo) {
|
|
|
+ nodeHttpAddress = containerInfo._data.nodeHttpAddress;
|
|
|
+ nodeHttpAddress = nodeHttpAddress
|
|
|
+ .replace(/(^\w+:|^)\/\//, '');
|
|
|
+ containerNodeMapping = Ember.Object.create({
|
|
|
+ name: containerInfo.id,
|
|
|
+ value: nodeHttpAddress
|
|
|
+ });
|
|
|
+ this.containerNodeMappings.push(containerNodeMapping);
|
|
|
+ containerIdArr[containerInfo.id] = true;
|
|
|
+ }.bind(this));
|
|
|
+
|
|
|
+ containers = (containers || []).concat(
|
|
|
+ hash.rmContainers.get("content")
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ this.set("attemptContainerList", containers);
|
|
|
+ this.initializeSelect(".js-fetch-threaddump-containers");
|
|
|
+
|
|
|
+ if (containerId) {
|
|
|
+ this.send("showThreaddumpForContainer", containerId);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ this.set("_isLoadingTopPanel", false);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.set("attemptContainerList", null);
|
|
|
+ this.set("selectedContainerId", "");
|
|
|
+ this.set("containerLogFiles", null);
|
|
|
+ this.set("selectedLogFileName", "");
|
|
|
+ this.set("selectedContainerThreaddumpContent", "");
|
|
|
+ this.set("containerNodeMappings", []);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ showThreaddumpForContainer(containerIdForThreaddump) {
|
|
|
+ Ember.$("#logContentTextArea1234554321").val("");
|
|
|
+ this.set("showFullThreaddump", false);
|
|
|
+ this.set("selectedContainerId", containerIdForThreaddump);
|
|
|
+ this.set("_isLoadingBottomPanel", true);
|
|
|
+ this.set("_isStdoutLogsFetchFailed", false);
|
|
|
+ this.set("stdoutLogsFetchFailedError", "");
|
|
|
+ this.set("_isThreaddumpAttemptFailed", false);
|
|
|
+ this.set("threaddumpErrorMessage", "");
|
|
|
+
|
|
|
+ if (containerIdForThreaddump) {
|
|
|
+ this.set("selectedContainerThreaddumpContent", "");
|
|
|
+
|
|
|
+ this.fetchThreaddumpForContainer(containerIdForThreaddump)
|
|
|
+ .then(function() {
|
|
|
+ Ember.Logger.log("Threaddump attempt has been successful!");
|
|
|
+
|
|
|
+ var currentContainerId = null;
|
|
|
+ var currentContainerNode = null;
|
|
|
+ var nodeForContainerThreaddump = null;
|
|
|
+
|
|
|
+ // Fetch the NM node for the selected container from the
|
|
|
+ // mappings stored above when
|
|
|
+ this.containerNodeMappings.forEach(function(item) {
|
|
|
+ currentContainerId = item.name;
|
|
|
+ currentContainerNode = item.value;
|
|
|
+
|
|
|
+ if ((currentContainerId === containerIdForThreaddump)
|
|
|
+ && nodeForContainerThreaddump == null) {
|
|
|
+ nodeForContainerThreaddump = currentContainerNode;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (nodeForContainerThreaddump) {
|
|
|
+ // Fetch stdout logs after 1.2 seconds of a successful POST
|
|
|
+ // request to RM. This is to allow for sufficient time for the NM
|
|
|
+ // to heartbeat into RM (default of 1 second) whereupon it will
|
|
|
+ // receive RM's signal to post a threaddump that will be captured
|
|
|
+ // in the stdout logs of the container. The extra 200ms is to
|
|
|
+ // allow for any network/IO delays.
|
|
|
+ Ember.run.later((function() {
|
|
|
+ this.fetchLogsForContainer(nodeForContainerThreaddump,
|
|
|
+ containerIdForThreaddump,
|
|
|
+ "stdout")
|
|
|
+ .then(
|
|
|
+ hash => {
|
|
|
+ this.set("selectedContainerThreaddumpContent",
|
|
|
+ hash.logs.get('logs').trim());
|
|
|
+ }.bind(this))
|
|
|
+ .catch(function(error) {
|
|
|
+ this.set("_isStdoutLogsFetchFailed", true);
|
|
|
+ this.set("stdoutLogsFetchFailedError", error);
|
|
|
+ }.bind(this))
|
|
|
+ .finally(function() {
|
|
|
+ this.set("_isLoadingBottomPanel", false);
|
|
|
+ }.bind(this));
|
|
|
+ }.bind(this)), 1200);
|
|
|
+ }
|
|
|
+ }.bind(this), function(error) {
|
|
|
+ this.set("_isThreaddumpAttemptFailed", true);
|
|
|
+ this.set("threaddumpErrorMessage", error);
|
|
|
+ this.set("_isLoadingBottomPanel", false);
|
|
|
+ }.bind(this)).finally(function() {
|
|
|
+ this.set("selectedContainerThreaddumpContent", "");
|
|
|
+ }.bind(this));
|
|
|
+ } else {
|
|
|
+ this.set("selectedContainerId", "");
|
|
|
+ this.set("selectedContainerThreaddumpContent", "");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // Set up the running attempt for the first time threaddump button is clicked.
|
|
|
+ runningAttempt: Ember.computed("model.attempts", function() {
|
|
|
+ let attempts = this.get("model.attempts");
|
|
|
+ let runningAttemptId = null;
|
|
|
+
|
|
|
+ if (attempts && attempts.get("length") && attempts.get("content")) {
|
|
|
+ attempts.get("content").forEach(function(appAttempt) {
|
|
|
+ if (appAttempt._data.state === "RUNNING") {
|
|
|
+ runningAttemptId = appAttempt._data.appAttemptId;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return runningAttemptId;
|
|
|
+ }),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create and send fetch running containers request.
|
|
|
+ */
|
|
|
+ fetchRunningContainersForAttemptId(attemptId) {
|
|
|
+ let request = {};
|
|
|
+
|
|
|
+ request["rmContainers"] = this.store
|
|
|
+ .query("yarn-container", {
|
|
|
+ app_attempt_id: attemptId
|
|
|
+ })
|
|
|
+ .catch(function(error) {
|
|
|
+ return Ember.A();
|
|
|
+ });
|
|
|
+
|
|
|
+ return Ember.RSVP.hash(request);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create and send fetch logs request for a selected container, from a
|
|
|
+ * specific node.
|
|
|
+ */
|
|
|
+ fetchLogsForContainer(nodeForContainer, containerId, logFile) {
|
|
|
+ let request = {};
|
|
|
+
|
|
|
+ request["logs"] = this.store
|
|
|
+ .queryRecord("yarn-node-container-log", {
|
|
|
+ nodeHttpAddr: nodeForContainer,
|
|
|
+ containerId: containerId,
|
|
|
+ fileName: logFile
|
|
|
+ })
|
|
|
+
|
|
|
+ return Ember.RSVP.hash(request);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Send out a POST request to RM to signal a threaddump for the selected
|
|
|
+ * container for the currently logged-in user.
|
|
|
+ */
|
|
|
+ fetchThreaddumpForContainer(containerId) {
|
|
|
+ var adapter = this.store.adapterFor("yarn-container-threaddump");
|
|
|
+
|
|
|
+ let requestedUser = "";
|
|
|
+ if (this.model
|
|
|
+ && this.model.userInfo
|
|
|
+ && this.model.userInfo.get('firstObject')) {
|
|
|
+ requestedUser = this.model.userInfo
|
|
|
+ .get('firstObject')
|
|
|
+ .get('requestedUser');
|
|
|
+ }
|
|
|
+
|
|
|
+ return adapter.signalContainerForThreaddump({}, containerId, requestedUser);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Reset attributes after a refresh.
|
|
|
+ */
|
|
|
+ resetAfterRefresh() {
|
|
|
+ this.set("selectedAttemptId", "");
|
|
|
+ this.set("attemptContainerList", null);
|
|
|
+ this.set("selectedContainerId", "");
|
|
|
+ this.set("selectedLogFileName", "");
|
|
|
+ this.set("containerLogFiles", null);
|
|
|
+ this.set("selectedContainerThreaddumpContent", "");
|
|
|
+ this.set("containerNodeMappings", []);
|
|
|
+ this.set("_isThreaddumpAttemptFailed", false);
|
|
|
+ this.set("threaddumpErrorMessage", "");
|
|
|
+ this.set("_isStdoutLogsFetchFailed", false);
|
|
|
+ this.set("stdoutLogsFetchFailedError", "");
|
|
|
+ this.set("appAttemptToStateMappings", []);
|
|
|
+ },
|
|
|
+
|
|
|
+ // Set the threaddump content in the content area.
|
|
|
+ showThreaddumpContent: Ember.computed(
|
|
|
+ "selectedContainerThreaddumpContent",
|
|
|
+ function() {
|
|
|
+ return this.get("selectedContainerThreaddumpContent");
|
|
|
+ }
|
|
|
+ ),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check if the application for the container whose threaddump is being attempted
|
|
|
+ * is even running.
|
|
|
+ */
|
|
|
+ isRunningApp: Ember.computed('model.app.state', function() {
|
|
|
+ return this.get('model.app.state') === "RUNNING";
|
|
|
+ })
|
|
|
+});
|