You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by bi...@apache.org on 2019/04/13 15:57:21 UTC

[hadoop] branch trunk updated: YARN-9281. Add express upgrade button to Appcatalog UI. Contributed by Eric Yang

This is an automated email from the ASF dual-hosted git repository.

billie pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/hadoop.git


The following commit(s) were added to refs/heads/trunk by this push:
     new b2cdf80  YARN-9281. Add express upgrade button to Appcatalog UI. Contributed by Eric Yang
b2cdf80 is described below

commit b2cdf809bce10f680048cacf45806f0abb4f4804
Author: Billie Rinaldi <bi...@apache.org>
AuthorDate: Sat Apr 13 18:55:11 2019 +0300

    YARN-9281. Add express upgrade button to Appcatalog UI. Contributed by Eric Yang
---
 .../pom.xml                                        |   1 -
 .../application/AppCatalogSolrClient.java          |  83 +++++++++++----
 .../appcatalog/application/YarnServiceClient.java  |  22 ++++
 .../controller/AppDetailsController.java           |  34 ++++++
 .../src/main/javascript/app.js                     |   3 +
 .../src/main/javascript/controllers.js             |  54 ++++++++++
 .../src/main/webapp/css/bootstrap-hadoop.css       |   5 +
 .../src/main/webapp/partials/details.html          |   3 +-
 .../src/main/webapp/partials/upgrade.html          | 114 +++++++++++++++++++++
 .../src/main/webapp/theme.html                     |   2 +-
 .../application/TestAppCatalogSolrClient.java      |  19 ++++
 .../controller/AppDetailsControllerTest.java       |  19 ++++
 .../configsets/exampleCollection/conf/schema.xml   |   1 +
 13 files changed, 336 insertions(+), 24 deletions(-)

diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/pom.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/pom.xml
index 58646bc..1ca403a 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/pom.xml
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/pom.xml
@@ -487,5 +487,4 @@
         </build>
       </profile>
     </profiles>
-
 </project>
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/application/AppCatalogSolrClient.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/application/AppCatalogSolrClient.java
index f3532ae..b1515a5 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/application/AppCatalogSolrClient.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/application/AppCatalogSolrClient.java
@@ -291,13 +291,12 @@ public class AppCatalogSolrClient {
       docs.add(request);
     }
 
-    // Commit Solr changes.
-    UpdateResponse detailsResponse = solr.add(docs);
-    if (detailsResponse.getStatus() != 0) {
+    try {
+      commitSolrChanges(solr, docs);
+    } catch (IOException e) {
       throw new IOException("Unable to register docker instance "
-          + "with application entry.");
+          + "with application entry.", e);
     }
-    solr.commit();
   }
 
   private SolrInputDocument incrementDownload(SolrDocument doc,
@@ -350,16 +349,10 @@ public class AppCatalogSolrClient {
       buffer.setField("yarnfile_s", mapper.writeValueAsString(yarnApp));
 
       docs.add(buffer);
-      // Commit Solr changes.
-      UpdateResponse detailsResponse = solr.add(docs);
-      if (detailsResponse.getStatus() != 0) {
-        throw new IOException("Unable to register application " +
-            "in Application Store.");
-      }
-      solr.commit();
+      commitSolrChanges(solr, docs);
     } catch (SolrServerException | IOException e) {
       throw new IOException("Unable to register application " +
-          "in Application Store. "+ e.getMessage());
+          "in Application Store. ", e);
     }
   }
 
@@ -389,16 +382,64 @@ public class AppCatalogSolrClient {
       buffer.setField("yarnfile_s", mapper.writeValueAsString(yarnApp));
 
       docs.add(buffer);
-      // Commit Solr changes.
-      UpdateResponse detailsResponse = solr.add(docs);
-      if (detailsResponse.getStatus() != 0) {
-        throw new IOException("Unable to register application " +
-            "in Application Store.");
-      }
-      solr.commit();
+      commitSolrChanges(solr, docs);
     } catch (SolrServerException | IOException e) {
       throw new IOException("Unable to register application " +
-          "in Application Store. "+ e.getMessage());
+          "in Application Store. ", e);
     }
   }
+
+  public void upgradeApp(Service service) throws IOException,
+      SolrServerException {
+    ObjectMapper mapper = new ObjectMapper();
+    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+    Collection<SolrInputDocument> docs = new HashSet<SolrInputDocument>();
+    SolrClient solr = getSolrClient();
+    if (service!=null) {
+      String name = service.getName();
+      String app = "";
+      SolrQuery query = new SolrQuery();
+      query.setQuery("id:" + name);
+      query.setFilterQueries("type_s:AppEntry");
+      query.setRows(1);
+
+      QueryResponse response;
+      try {
+        response = solr.query(query);
+        Iterator<SolrDocument> appList = response.getResults().listIterator();
+        while (appList.hasNext()) {
+          SolrDocument d = appList.next();
+          app = d.get("app_s").toString();
+        }
+      } catch (SolrServerException | IOException e) {
+        LOG.error("Error in finding deployed application: " + name, e);
+      }
+      // Register deployed application instance with AppList
+      SolrInputDocument request = new SolrInputDocument();
+      request.addField("type_s", "AppEntry");
+      request.addField("id", name);
+      request.addField("name_s", name);
+      request.addField("app_s", app);
+      request.addField("yarnfile_s", mapper.writeValueAsString(service));
+      docs.add(request);
+    }
+    try {
+      commitSolrChanges(solr, docs);
+    } catch (IOException e) {
+      throw new IOException("Unable to register docker instance "
+          + "with application entry.", e);
+    }
+  }
+
+  private void commitSolrChanges(SolrClient solr,
+      Collection<SolrInputDocument> docs)
+          throws IOException, SolrServerException {
+    // Commit Solr changes.
+    UpdateResponse detailsResponse = solr.add(docs);
+    if (detailsResponse.getStatus() != 0) {
+      throw new IOException("Failed to commit document in solr, status code: "
+          + detailsResponse.getStatus());
+    }
+    solr.commit();
+  }
 }
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/application/YarnServiceClient.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/application/YarnServiceClient.java
index 667d218..3a6c67d 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/application/YarnServiceClient.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/application/YarnServiceClient.java
@@ -27,6 +27,7 @@ import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.yarn.appcatalog.model.AppEntry;
 import org.apache.hadoop.yarn.service.api.records.Service;
+import org.apache.hadoop.yarn.service.api.records.ServiceState;
 import org.apache.hadoop.yarn.service.api.records.KerberosPrincipal;
 import org.apache.hadoop.yarn.service.client.ApiServiceClient;
 
@@ -171,4 +172,25 @@ public class YarnServiceClient {
       LOG.error("Error in fetching application status: ", e);
     }
   }
+
+  public void upgradeApp(Service app) throws JsonProcessingException {
+    ObjectMapper mapper = new ObjectMapper();
+    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+    String appInstanceId = app.getName();
+    app.setState(ServiceState.EXPRESS_UPGRADING);
+    String yarnFile = mapper.writeValueAsString(app);
+    ClientResponse response;
+    try {
+      response = asc.getApiClient(asc.getServicePath(appInstanceId))
+          .put(ClientResponse.class, yarnFile);
+      if (response.getStatus() >= 299) {
+        String message = response.getEntity(String.class);
+        throw new RuntimeException("Failed : HTTP error code : "
+            + response.getStatus() + " error: " + message);
+      }
+    } catch (UniformInterfaceException | ClientHandlerException
+        | IOException e) {
+      LOG.error("Error in stopping application: ", e);
+    }
+  }
 }
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/controller/AppDetailsController.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/controller/AppDetailsController.java
index db6973d..63aefa6 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/controller/AppDetailsController.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/java/org/apache/hadoop/yarn/appcatalog/controller/AppDetailsController.java
@@ -18,8 +18,12 @@
 
 package org.apache.hadoop.yarn.appcatalog.controller;
 
+import java.io.IOException;
+
+import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
@@ -32,6 +36,7 @@ import org.apache.hadoop.yarn.appcatalog.application.YarnServiceClient;
 import org.apache.hadoop.yarn.appcatalog.model.AppEntry;
 import org.apache.hadoop.yarn.service.api.records.Service;
 import org.apache.hadoop.yarn.service.api.records.ServiceState;
+import org.apache.solr.client.solrj.SolrServerException;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 
@@ -262,4 +267,33 @@ public class AppDetailsController {
     }
     return Response.ok().build();
   }
+
+  /**
+   * Upgrade an application.
+   *
+   * @apiGroup AppDetailController
+   * @apiName upgradeApp
+   * @api {put} /app_details/upgrade/{id} Upgrade one instance of application.
+   * @apiParam {String} id Application Name to upgrade.
+   * @apiSuccess {String} text
+   * @apiError BadRequest Requested application does not upgrade.
+   * @return Web response code
+   */
+  @Path("upgrade/{id}")
+  @PUT
+  @Consumes(MediaType.APPLICATION_JSON)
+  @Produces(MediaType.APPLICATION_JSON)
+  public Response upgradeApp(@PathParam("id") String id, Service app) {
+    try {
+      AppCatalogSolrClient sc = new AppCatalogSolrClient();
+      sc.upgradeApp(app);
+      YarnServiceClient yc = new YarnServiceClient();
+      yc.upgradeApp(app);
+    } catch (IOException | SolrServerException e) {
+      return Response.status(Status.BAD_REQUEST).entity(e.toString()).build();
+    }
+    String output = "{\"status\":\"Application upgrade requested.\",\"id\":\"" +
+        app.getName() + "\"}";
+    return Response.status(Status.ACCEPTED).entity(output).build();
+  }
 }
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/javascript/app.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/javascript/app.js
index 0a7b6db..4cab2b4 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/javascript/app.js
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/javascript/app.js
@@ -53,6 +53,9 @@ app.config(['$routeProvider',
     }).when('/deploy/:id', {
       templateUrl: 'partials/deploy.html',
       controller: 'DeployAppController'
+    }).when('/upgrade/:id', {
+      templateUrl: 'partials/upgrade.html',
+      controller: 'UpgradeAppController'
     }).otherwise({
       redirectTo: '/'
     });
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/javascript/controllers.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/javascript/controllers.js
index 241f15d..4d9e42d 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/javascript/controllers.js
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/javascript/controllers.js
@@ -127,6 +127,10 @@ controllers.controller("AppDetailsController", [ '$scope', '$interval', '$rootSc
         }, errorCallback);
       }
 
+      $scope.upgradeApp = function(id) {
+        window.location = '/#!/upgrade/' + id;
+      }
+
       $scope.canDeployApp = function() {
         return true;
       };
@@ -306,6 +310,56 @@ controllers.controller("DeployAppController", [ '$scope', '$rootScope', '$http',
 
 }]);
 
+controllers.controller("UpgradeAppController", [ '$scope', '$rootScope', '$http',
+    '$routeParams', function($scope, $rootScope, $http, $routeParams) {
+    $scope.message = null;
+    $scope.error = null;
+    $scope.appName = $routeParams.id;
+    $scope.refreshAppDetails = function() {
+      $http({
+        method : 'GET',
+        url : '/v1/app_details/status/' + $scope.appName
+      }).then(successCallback, errorCallback);
+    }
+
+    $scope.upgradeApp = function(app) {
+      $rootScope.$emit("showLoadScreen", {});
+      $http({
+        method : 'PUT',
+        url : '/v1/app_details/upgrade/' + $scope.appName,
+        data : JSON.stringify($scope.details)
+      }).then(function(data, status, headers, config) {
+        $rootScope.$emit("RefreshAppList", {});
+        window.location = '/#!/app/' + data.data.id;
+      }, function(data, status, headers, config) {
+        $rootScope.$emit("hideLoadScreen", {});
+        $scope.error = data.data;
+        $('#error-message').html(data.data);
+        $('#myModal').modal('show');
+        console.log('error', data, status);
+      });
+    }
+
+    function successCallback(response) {
+      if (response.data.yarnfile.components.length!=0) {
+        $scope.details = response.data.yarnfile;
+      } else {
+        // When application is in accepted or failed state, it does not
+        // have components detail, hence we update states only.
+        $scope.details.state = response.data.yarnfile.state;
+      }
+    }
+
+    function errorCallback(response) {
+      $rootScope.$emit("hideLoadScreen", {});
+      $scope.error = "Error in getting application detail.";
+      $('#error-message').html($scope.error);
+      $('#myModal').modal('show');
+    }
+
+    $scope.refreshAppDetails();
+}]);
+
 controllers.controller("LoadScreenController", [ '$scope', '$rootScope', '$http', function($scope, $rootScope, $http) {
   $scope.loadScreen = "hide";
 
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/css/bootstrap-hadoop.css b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/css/bootstrap-hadoop.css
index 5aa6e46..231f9a9 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/css/bootstrap-hadoop.css
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/css/bootstrap-hadoop.css
@@ -184,6 +184,11 @@
   background-color: #FFF;
   box-shadow: 0 0 2px 0 #1391c1;
 }
+.btn-secondary:visited {
+  color: #429929;
+  background-color: #FFF;
+  box-shadow: 0 0 2px 0 #1391c1;
+}
 .btn-secondary[disabled],
 .btn-secondary:focus[disabled],
 .btn-secondary.disabled,
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/partials/details.html b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/partials/details.html
index 69ef912..8624440 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/partials/details.html
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/partials/details.html
@@ -14,8 +14,9 @@
 <div class="container content">
   <div class="row">
     <div class="col-xs-12 col-md-12 col-lg-12">
+      <a ng-click="restartApp(appName)" class="btn btn-secondary"><span class="glyphicon glyphicon-play"></span> Start</a>
       <a ng-click="stopApp(appName)" class="btn btn-secondary"><span class="glyphicon glyphicon-stop"></span> Stop</a>
-      <a ng-click="restartApp(appName)" class="btn btn-secondary"><span class="glyphicon glyphicon-refresh"></span> Start</a>
+      <a ng-click="upgradeApp(appName)" class="btn btn-secondary"><span class="glyphicon glyphicon-circle-arrow-up"></span> Upgrade</a>
       <div style="display:inline-block;" ng-repeat="(key, value) in details.yarnfile.quicklinks">
         <a href="{{value}}" class="btn btn-secondary" ng-hide="checkServiceLink()"><span class="glyphicon glyphicon-new-window"></span> {{key}}</a>
       </div>
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/partials/upgrade.html b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/partials/upgrade.html
new file mode 100644
index 0000000..2453aab
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/partials/upgrade.html
@@ -0,0 +1,114 @@
+<!---
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License. See accompanying LICENSE file.
+-->
+<div class="container content">
+    <div class="row">
+      <div class="col-xs-12 col-md-12 col-lg-12">
+        <div class="form-group">
+          <h1>Upgrade Application</h1>
+        </div>
+        <div class="form-group">
+          <label>Application Name</label>
+          <input type=text name="name" class="form-control" ng-model="details.name" readonly />
+        </div>
+        <div class="form-group">
+          <label>Version</label>
+          <input type=text name="version" class="form-control" ng-model="details.version" />
+        </div>
+        <div class="form-group">
+          <label>Quick Link</label>
+          <textarea json-text name="quicklink" class="form-control" ng-model="details.quicklinks"/></textarea>
+        </div>
+      </div>
+    </div>
+
+    <div class="row">
+      <div class="col-xs-12 col-md-12 col-lg-12" ng-repeat="docker in details.components track by $index">
+        <div class="panel">
+          <div class="form-group">
+            <label>Component Name</label>
+            <input type=text name="name" class="form-control" ng-model="docker.name" readonly />
+          </div>
+          <div class="form-group">
+            <label>Artifact</label>
+            <input type=text name="artifact_id" class="form-control" ng-model="docker.artifact.id" />
+          </div>
+          <div class="form-group">
+            <label>Number of containers</label>
+            <input type=text name="artifact_id" class="form-control" ng-model="docker.number_of_containers" readonly />
+          </div>
+          <div class="form-group">
+            <label>Launch Command</label>
+            <input type=text name="launch_command" class="form-control" ng-model="docker.launch_command" />
+          </div>
+          <div class="form-group">
+            <label>CPU</label>
+            <input type=text name="cpus" class="form-control" ng-model="docker.resource.cpus" readonly />
+          </div>
+          <div class="form-group">
+            <label>Memory</label>
+            <input type=text name="memory" class="form-control" ng-model="docker.resource.memory" readonly />
+          </div>
+          <div class="form-group">
+            <input type="checkbox" ng-attr-id="{{'checkbox-priv-' + $index}}" ng-model="docker.run_privileged_container">
+            <label for="checkbox-priv-{{$index}}"> Privileged Container</label>
+          </div>
+          <div class="form-group">
+            <label>Dependencies</label>
+            <input json-text type=text name="dependencies" class="form-control" ng-model="docker.dependencies" />
+          </div>
+          <div class="form-group">
+            <label>Placement Policy</label>
+            <input type=text name="placement" class="form-control" ng-model="docker.placement_policy.constraints" readonly />
+          </div>
+          <div class="form-group">
+            <label>Environments</label>
+            <textarea json-text name="env" class="form-control" ng-model="docker.configuration.env"/></textarea>
+          </div>
+          <div class="form-group">
+            <label>Properties</label>
+            <textarea json-text name="properties" class="form-control" ng-model="docker.configuration.properties"/></textarea>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <br>
+    <div class="row">
+      <div class="col-xs-12 col-md-12 col-lg-12">
+        <p>
+        <a class="btn btn-secondary" ng-click="upgradeApp(details)">Upgrade</a>
+        <a class="btn btn-secondary" href="/#!/app/{{details.name}}">CANCEL</a>
+      </div>
+    </div>
+
+    <div class="modal fade" id="myModal" role="dialog">
+      <div class="modal-dialog">
+        <!-- Modal content-->
+        <div class="modal-content">
+          <div class="modal-header">
+            <button type="button" class="close" data-dismiss="modal">&times;</button>
+            <h4 class="modal-title" ng-if="message">Info</h4>
+            <h4 class="modal-title" ng-if="error">Error</h4>
+          </div>
+          <div class="modal-body">
+            <p class="infobox bg-info" ng-if="message">{{message}}</p>
+            <div id="error-message"></div>
+          </div>
+          <div class="modal-footer">
+            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+          </div>
+        </div>
+      </div>
+    </div>
+</div>
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/theme.html b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/theme.html
index a8a2a2d..57982d1 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/theme.html
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/theme.html
@@ -29,7 +29,7 @@
 
     <!-- Custom styles for this template -->
     <link href="css/theme.css" rel="stylesheet">
-    <link href="css/bootstrap-hadoop.min.css" rel="stylesheet">
+    <link href="css/bootstrap-hadoop.css" rel="stylesheet">
 
 </head>
 
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/java/org/apache/hadoop/yarn/appcatalog/application/TestAppCatalogSolrClient.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/java/org/apache/hadoop/yarn/appcatalog/application/TestAppCatalogSolrClient.java
index 16bf7fb..d902de5 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/java/org/apache/hadoop/yarn/appcatalog/application/TestAppCatalogSolrClient.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/java/org/apache/hadoop/yarn/appcatalog/application/TestAppCatalogSolrClient.java
@@ -18,6 +18,7 @@
 
 package org.apache.hadoop.yarn.appcatalog.application;
 
+import org.apache.hadoop.yarn.appcatalog.model.AppEntry;
 import org.apache.hadoop.yarn.appcatalog.model.AppStoreEntry;
 import org.apache.hadoop.yarn.appcatalog.model.Application;
 import org.apache.solr.client.solrj.SolrClient;
@@ -127,4 +128,22 @@ public class TestAppCatalogSolrClient {
     }
   }
 
+  @Test
+  public void testUpgradeApp() throws Exception {
+    Application example = new Application();
+    String expected = "2.0";
+    String actual = "";
+    example.setOrganization("jenkins-ci.org");
+    example.setVersion("1.0");
+    example.setName("jenkins");
+    example.setDescription("World leading open source automation system.");
+    example.setIcon("/css/img/feather.png");
+    spy.register(example);
+    spy.deployApp("test", example);
+    example.setVersion("2.0");
+    spy.upgradeApp(example);
+    List<AppEntry> appEntries = spy.listAppEntries();
+    actual = appEntries.get(appEntries.size() -1).getYarnfile().getVersion();
+    assertEquals(expected, actual);
+  }
 }
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/java/org/apache/hadoop/yarn/appcatalog/controller/AppDetailsControllerTest.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/java/org/apache/hadoop/yarn/appcatalog/controller/AppDetailsControllerTest.java
index ca4fba9..437d50b 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/java/org/apache/hadoop/yarn/appcatalog/controller/AppDetailsControllerTest.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/java/org/apache/hadoop/yarn/appcatalog/controller/AppDetailsControllerTest.java
@@ -135,4 +135,23 @@ public class AppDetailsControllerTest {
         is("/app_details"));
   }
 
+  @Test
+  public void testUpgradeApp() throws Exception {
+    String id = "application1";
+    AppDetailsController ac = Mockito.mock(AppDetailsController.class);
+
+    Service yarnfile = new Service();
+    yarnfile.setVersion("1.0");
+    Component comp = new Component();
+    Container c = new Container();
+    c.setId("container-1");
+    List<Container> containers = new ArrayList<Container>();
+    containers.add(c);
+    comp.setContainers(containers);
+    yarnfile.addComponent(comp);
+    Response expected = Response.ok().build();
+    when(ac.upgradeApp(id, yarnfile)).thenReturn(Response.ok().build());
+    final Response actual = ac.upgradeApp(id, yarnfile);
+    assertEquals(expected.getStatus(), actual.getStatus());
+  }
 }
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/schema.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/schema.xml
index 20acbc9..0921e13 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/schema.xml
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/schema.xml
@@ -23,6 +23,7 @@
     <field name="type_s" type="string" indexed="true" stored="true" required="false" multiValued="false" docValues="true" />
     <field name="org_s" type="string" indexed="true" stored="true" required="false" multiValued="false" docValues="true" />
     <field name="name_s" type="string" indexed="true" stored="true" required="false" multiValued="false" docValues="true" />
+    <field name="app_s" type="string" indexed="true" stored="true" required="false" multiValued="false" docValues="true" />
     <field name="desc_s" type="string" indexed="true" stored="true" required="false" multiValued="false" docValues="true" />
     <field name="icon_s" type="string" indexed="true" stored="true" required="false" multiValued="false" docValues="true" />
     <field name="yarnfile_s" type="string" indexed="true" stored="true" required="false" multiValued="false" docValues="true" />


---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org