+ * 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.
+ *
+ */
+/**
+ * Application Catalog controllers.
+ */
+package org.apache.hadoop.yarn.appcatalog.controller;
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/model/AppDetails.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/model/AppDetails.java
new file mode 100644
index 0000000000..42012cfb81
--- /dev/null
+++ 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/model/AppDetails.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+package org.apache.hadoop.yarn.appcatalog.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * Data model for user defined application configuration.
+ */
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
+public class AppDetails {
+ private String image;
+ private String version;
+ private String[] ports;
+ private String[] volumes;
+ private String[] env;
+
+ public String getImage() {
+ return image;
+ }
+
+ public void setImage(String image) {
+ this.image = image;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String[] getPorts() {
+ return ports;
+ }
+
+ public void setPorts(String[] ports2) {
+ this.ports = ports2;
+ }
+
+ public String[] getVolumes() {
+ return volumes;
+ }
+
+ public void setVolumes(String[] volumes) {
+ this.volumes = volumes;
+ }
+
+ public String[] getEnv() {
+ return env;
+ }
+
+ public void setEnv(String[] env) {
+ this.env = env;
+ }
+}
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/model/AppEntry.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/model/AppEntry.java
new file mode 100644
index 0000000000..acba4d9a8b
--- /dev/null
+++ 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/model/AppEntry.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+package org.apache.hadoop.yarn.appcatalog.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.hadoop.yarn.service.api.records.Service;
+
+/**
+ * Data model for deployed application.
+ *
+ */
+@XmlType(namespace="http://hadoop.apache.org", name="AppEntry")
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
+public class AppEntry {
+ private String id;
+ private String name;
+ private String app;
+ private Service yarnfile;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getApp() {
+ return app;
+ }
+
+ public void setApp(String app) {
+ this.app = app;
+ }
+
+ public Service getYarnfile() {
+ return yarnfile;
+ }
+
+ public void setYarnfile(Service yarnfile) {
+ this.yarnfile = yarnfile;
+ }
+
+}
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/model/AppStoreEntry.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/model/AppStoreEntry.java
new file mode 100644
index 0000000000..f245297600
--- /dev/null
+++ 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/model/AppStoreEntry.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+package org.apache.hadoop.yarn.appcatalog.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.apache.hadoop.yarn.service.api.records.Service;
+
+/**
+ * Data model of application template stored in application catalog.
+ */
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
+public class AppStoreEntry {
+ private String id;
+ private String org;
+ private String name;
+ private String desc;
+ private String icon = "/css/img/feather.png";
+ private long like;
+ private long download;
+ private Service app;
+
+ public String getOrg() {
+ return org;
+ }
+
+ public void setOrg(String org) {
+ this.org = org;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+
+ public void setDesc(String desc) {
+ this.desc = desc;
+ }
+
+ public long getLike() {
+ return like;
+ }
+
+ public void setLike(long like) {
+ this.like = like;
+ }
+
+ public long getDownload() {
+ return download;
+ }
+
+ public void setDownload(long download) {
+ this.download = download;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Service getApp() {
+ return app;
+ }
+
+ public void setApp(Service app) {
+ this.app = app;
+ }
+
+ public String getIcon() {
+ return icon;
+ }
+
+ public void setIcon(String icon) {
+ this.icon = icon;
+ }
+
+}
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/model/Application.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/model/Application.java
new file mode 100644
index 0000000000..dbc31b147e
--- /dev/null
+++ 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/model/Application.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+package org.apache.hadoop.yarn.appcatalog.model;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import org.apache.hadoop.yarn.service.api.records.Service;
+
+/**
+ * Data model of display recommended applications and descriptions.
+ */
+@XmlRootElement
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonPropertyOrder({ "organization", "name", "description", "icon" })
+public class Application extends Service {
+ private static final long serialVersionUID = -1776203219305414248L;
+
+ private String organization;
+ private String description;
+ private String icon;
+
+ @JsonProperty("organization")
+ public String getOrganization() {
+ return organization;
+ }
+ public void setOrganization(String organization) {
+ this.organization = organization;
+ }
+
+ @JsonProperty("description")
+ public String getDescription() {
+ return description;
+ }
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ @JsonProperty("icon")
+ public String getIcon() {
+ return icon;
+ }
+ public void setIcon(String icon) {
+ this.icon = icon;
+ }
+
+}
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/model/package-info.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/model/package-info.java
new file mode 100644
index 0000000000..9a20b99384
--- /dev/null
+++ 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/model/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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
+ *
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/js/bootstrap-hadoop.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/js/bootstrap-hadoop.js
new file mode 100644
index 0000000000..bd57d5c86a
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/js/bootstrap-hadoop.js
@@ -0,0 +1,284 @@
+/**
+ * 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.
+ */
+'use strict';
+
+$(document).ready(function () {
+ var $accordionToggler = $(this).find('[data-toggle="collapseAccordion"]');
+ $accordionToggler.off('click').on('click', function (event) {
+ var $this = $(this);
+ $this.siblings('.panel-body').slideToggle(500);
+ $this.children().children('.panel-toggle').toggleClass('fa-angle-down fa-angle-up');
+ event.stopPropagation();
+ return false;
+ });
+});
+'use strict';
+
+(function ($) {
+
+ /**
+ * jQuery plugin for navigation bars
+ * Usage:
+ *
+ * $('.navigation-bar').navigationBar();
+ *
+ *
+ * @param {object} options see $.fn.navigationBar.defaults
+ * @returns {$}
+ */
+
+ $.fn.navigationBar = function (options) {
+
+ var settings = $.extend({}, $.fn.navigationBar.defaults, options);
+
+ return this.each(function () {
+ var _this = this;
+
+ var containerSelector = '.navigation-bar-container';
+ var $navigationContainer = $(this).find(containerSelector);
+ var $sideNavToggler = $(this).find('[data-toggle=' + settings.navBarToggleDataAttr + ']');
+ var $subMenuToggler = $(this).find('[data-toggle=' + settings.subMenuNavToggleDataAttr + ']');
+ var firstLvlMenuItemsSelector = '.side-nav-menu>li';
+ var secondLvlMenuItemsSelector = '.side-nav-menu>li>ul>li';
+ var $moreActions = $(this).find('.more-actions');
+ var $dropdownMenu = $moreActions.children('.dropdown-menu');
+
+ $subMenuToggler.each(function (index, toggler) {
+ return $(toggler).parent().addClass('has-sub-menu');
+ });
+
+ if (settings.fitHeight) {
+ $(this).addClass('navigation-bar-fit-height');
+
+ // make scrolling effect on side nav ONLY, i.e. not effected on ambari main contents
+ $(this).find('.side-nav-menu').on('DOMMouseScroll mousewheel', function (ev) {
+ var $this = $(this),
+ scrollTop = this.scrollTop,
+ scrollHeight = this.scrollHeight,
+ height = $this.innerHeight(),
+ delta = ev.originalEvent.wheelDelta,
+ up = delta > 0;
+ var prevent = function prevent() {
+ ev.stopPropagation();
+ ev.preventDefault();
+ ev.returnValue = false;
+ return false;
+ };
+
+ if (!up && -delta > scrollHeight - height - scrollTop) {
+ // Scrolling down, but this will take us past the bottom.
+ $this.scrollTop(scrollHeight);
+ return prevent();
+ } else if (up && delta > scrollTop) {
+ // Scrolling up, but this will take us past the top.
+ $this.scrollTop(0);
+ return prevent();
+ }
+ });
+ }
+
+ //set main content left margin based on the width of side-nav
+ var containerWidth = $navigationContainer.width();
+ if (settings.moveLeftContent) {
+ $(settings.content).css('margin-left', containerWidth);
+ }
+ if (settings.moveLeftFooter) {
+ $(settings.footer).css('margin-left', containerWidth);
+ }
+
+ function popStateHandler() {
+ var path = window.location.pathname + window.location.hash;
+ $navigationContainer.find('li a').each(function (index, link) {
+ var $link = $(link);
+ var href = $link.attr('data-href') || $link.attr('href');
+ if (path.indexOf(href) !== -1 && ['', '#'].indexOf(href) === -1) {
+ $link.parent().addClass('active');
+ } else {
+ $link.parent().removeClass('active');
+ }
+ });
+ }
+
+ if (settings.handlePopState) {
+ popStateHandler();
+ $(window).bind('popstate', popStateHandler);
+ }
+
+ function clickHandler(el) {
+ var $li = $(el).parent();
+ var activeClass = settings.activeClass;
+
+ var activeMenuItems = firstLvlMenuItemsSelector + '.' + activeClass;
+ var activeSubMenuItems = secondLvlMenuItemsSelector + '.' + activeClass;
+ $navigationContainer.find(activeMenuItems).removeClass(activeClass);
+ $navigationContainer.find(activeSubMenuItems).removeClass(activeClass);
+ $li.addClass(activeClass);
+ }
+
+ /**
+ * Click on menu item
+ */
+ $(firstLvlMenuItemsSelector + '>a').on('click', function () {
+ clickHandler(this);
+ });
+
+ /**
+ * Click on sub menu item
+ */
+ $(secondLvlMenuItemsSelector + '>a').on('click', function () {
+ clickHandler(this);
+ $(this).parent().parent().parent().addClass(settings.activeClass);
+ });
+
+ /**
+ * Slider for sub menu
+ */
+ $subMenuToggler.off('click').on('click', function (event) {
+ // ignore click if navigation-bar is collapsed
+ if ($navigationContainer.hasClass('collapsed')) {
+ return false;
+ }
+ var $this = $(this);
+ $this.siblings('.sub-menu').slideToggle(600, function () {
+ var $topMenuItem = $this.parent();
+ var $subMenu = $topMenuItem.find('ul');
+ return $subMenu.is(':visible') ? $topMenuItem.removeClass('collapsed') : $topMenuItem.addClass('collapsed');
+ });
+ $this.children('.toggle-icon').toggleClass(settings.menuLeftClass + ' ' + settings.menuDownClass);
+ event.stopPropagation();
+ return false;
+ });
+
+ /**
+ * Hovering effects for "more actions icon": "..."
+ */
+ $(this).find('.mainmenu-li>a').hover(function () {
+ var $moreIcon = $(this).siblings('.more-actions');
+ if ($moreIcon.length && !$navigationContainer.hasClass('collapsed')) {
+ $moreIcon.css('display', 'inline-block');
+ }
+ }, function () {
+ var $moreIcon = $(this).siblings('.more-actions');
+ if ($moreIcon.length && !$navigationContainer.hasClass('collapsed')) {
+ $moreIcon.hide();
+ }
+ });
+ $moreActions.hover(function () {
+ $(this).css('display', 'inline-block');
+ });
+ if (settings.fitHeight) {
+ $moreActions.on('click', function () {
+ // set actions submenu position
+ var $moreIcon = $(this);
+ var $header = $('.side-nav-header');
+ $dropdownMenu.css({
+ top: $moreIcon.offset().top - $header.offset().top + 20 + 'px',
+ left: $moreIcon.offset().left + 'px'
+ });
+ });
+ }
+ $dropdownMenu.on('click', function () {
+ // some action was triggered, should hide this icon
+ var moreIcon = $(this).parent();
+ setTimeout(function () {
+ moreIcon.hide();
+ }, 1000);
+ });
+ $navigationContainer.children('.side-nav-menu').scroll(function () {
+ $moreActions.removeClass('open');
+ });
+
+ /**
+ * Expand/collapse navigation bar
+ */
+ $sideNavToggler.click(function () {
+
+ $navigationContainer.toggleClass('collapsed').promise().done(function () {
+ var subMenuSelector = 'ul.sub-menu';
+ var $subMenus = $navigationContainer.find(subMenuSelector);
+ var $subMenuItems = $navigationContainer.find('.side-nav-menu>li');
+ if ($navigationContainer.hasClass('collapsed')) {
+ // set sub menu invisible when collapsed
+ $subMenus.hide();
+ $moreActions.hide();
+ // set the hover effect when collapsed, should show sub-menu on hovering
+ $subMenuItems.hover(function () {
+ $(this).find(subMenuSelector).show();
+ // set sub-menu position
+ var $parent = $(this);
+ var $header = $('.side-nav-header');
+ if (settings.fitHeight) {
+ $(this).find(subMenuSelector).css({
+ position: 'fixed',
+ top: $parent.offset().top - $header.offset().top + 'px',
+ left: 50 + 'px'
+ });
+ }
+ }, function () {
+ $(this).find(subMenuSelector).hide();
+ });
+ } else {
+ // keep showing all sub menu
+ $subMenus.show().each(function (index, item) {
+ return $(item).parent().removeClass('collapsed');
+ });
+ $subMenuItems.unbind('mouseenter mouseleave');
+ $navigationContainer.find('.toggle-icon').removeClass(settings.menuLeftClass).addClass(settings.menuDownClass);
+ // set sub-menu position
+ if (settings.fitHeight) {
+ $(_this).find(subMenuSelector).css({
+ position: 'relative',
+ top: 0,
+ left: 0
+ });
+ }
+ }
+
+ $navigationContainer.on('transitionend', function () {
+ //set main content left margin based on the width of side-nav
+ var containerWidth = $navigationContainer.width();
+ if (settings.moveLeftContent) {
+ $(settings.content).css('margin-left', containerWidth);
+ }
+ if (settings.moveLeftFooter) {
+ $(settings.footer).css('margin-left', containerWidth);
+ }
+ });
+ $sideNavToggler.find('span').toggleClass(settings.collapseNavBarClass + ' ' + settings.expandNavBarClass);
+ });
+ return false;
+ });
+ });
+ };
+
+ $.fn.navigationBar.defaults = {
+ handlePopState: true,
+ fitHeight: false,
+ content: '#main',
+ footer: 'footer',
+ moveLeftContent: true,
+ moveLeftFooter: true,
+ menuLeftClass: 'glyphicon-menu-right',
+ menuDownClass: 'glyphicon-menu-down',
+ collapseNavBarClass: 'fa-angle-double-left',
+ expandNavBarClass: 'fa-angle-double-right',
+ activeClass: 'active',
+ navBarToggleDataAttr: 'collapse-side-nav',
+ subMenuNavToggleDataAttr: 'collapse-sub-menu'
+ };
+})(jQuery);
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/js/bootstrap-hadoop.min.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/js/bootstrap-hadoop.min.js
new file mode 100644
index 0000000000..687279945b
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/js/bootstrap-hadoop.min.js
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+"use strict";$(document).ready(function(){$(this).find('[data-toggle="collapseAccordion"]').off("click").on("click",function(n){var l=$(this);return l.siblings(".panel-body").slideToggle(500),l.children().children(".panel-toggle").toggleClass("fa-angle-down fa-angle-up"),n.stopPropagation(),!1})});
+"use strict";!function(e){e.fn.navigationBar=function(n){var t=e.extend({},e.fn.navigationBar.defaults,n);return this.each(function(){function n(){var n=window.location.pathname+window.location.hash;i.find("li a").each(function(t,a){var s=e(a),i=s.attr("data-href")||s.attr("href");-1!==n.indexOf(i)&&-1===["","#"].indexOf(i)?s.parent().addClass("active"):s.parent().removeClass("active")})}function a(n){var a=e(n).parent(),s=t.activeClass,o=r+"."+s,l=f+"."+s;i.find(o).removeClass(s),i.find(l).removeClass(s),a.addClass(s)}var s=this,i=e(this).find(".navigation-bar-container"),o=e(this).find("[data-toggle="+t.navBarToggleDataAttr+"]"),l=e(this).find("[data-toggle="+t.subMenuNavToggleDataAttr+"]"),r=".side-nav-menu>li",f=".side-nav-menu>li>ul>li",c=e(this).find(".more-actions"),u=c.children(".dropdown-menu");l.each(function(n,t){return e(t).parent().addClass("has-sub-menu")}),t.fitHeight&&(e(this).addClass("navigation-bar-fit-height"),e(this).find(".side-nav-menu").on("DOMMouseScroll mousewheel",function(n){var t=e(this),a=this.scrollTop,s=this.scrollHeight,i=t.innerHeight(),o=n.originalEvent.wheelDelta,l=o>0,r=function(){return n.stopPropagation(),n.preventDefault(),n.returnValue=!1,!1};return!l&&-o>s-i-a?(t.scrollTop(s),r()):l&&o>a?(t.scrollTop(0),r()):void 0}));var d=i.width();t.moveLeftContent&&e(t.content).css("margin-left",d),t.moveLeftFooter&&e(t.footer).css("margin-left",d),t.handlePopState&&(n(),e(window).bind("popstate",n)),e(r+">a").on("click",function(){a(this)}),e(f+">a").on("click",function(){a(this),e(this).parent().parent().parent().addClass(t.activeClass)}),l.off("click").on("click",function(n){if(i.hasClass("collapsed"))return!1;var a=e(this);return a.siblings(".sub-menu").slideToggle(600,function(){var e=a.parent();return e.find("ul").is(":visible")?e.removeClass("collapsed"):e.addClass("collapsed")}),a.children(".toggle-icon").toggleClass(t.menuLeftClass+" "+t.menuDownClass),n.stopPropagation(),!1}),e(this).find(".mainmenu-li>a").hover(function(){var n=e(this).siblings(".more-actions");n.length&&!i.hasClass("collapsed")&&n.css("display","inline-block")},function(){var n=e(this).siblings(".more-actions");n.length&&!i.hasClass("collapsed")&&n.hide()}),c.hover(function(){e(this).css("display","inline-block")}),t.fitHeight&&c.on("click",function(){var n=e(this),t=e(".side-nav-header");u.css({top:n.offset().top-t.offset().top+20+"px",left:n.offset().left+"px"})}),u.on("click",function(){var n=e(this).parent();setTimeout(function(){n.hide()},1e3)}),i.children(".side-nav-menu").scroll(function(){c.removeClass("open")}),o.click(function(){return i.toggleClass("collapsed").promise().done(function(){var n=i.find("ul.sub-menu"),a=i.find(".side-nav-menu>li");i.hasClass("collapsed")?(n.hide(),c.hide(),a.hover(function(){e(this).find("ul.sub-menu").show();var n=e(this),a=e(".side-nav-header");t.fitHeight&&e(this).find("ul.sub-menu").css({position:"fixed",top:n.offset().top-a.offset().top+"px",left:"50px"})},function(){e(this).find("ul.sub-menu").hide()})):(n.show().each(function(n,t){return e(t).parent().removeClass("collapsed")}),a.unbind("mouseenter mouseleave"),i.find(".toggle-icon").removeClass(t.menuLeftClass).addClass(t.menuDownClass),t.fitHeight&&e(s).find("ul.sub-menu").css({position:"relative",top:0,left:0})),i.on("transitionend",function(){var n=i.width();t.moveLeftContent&&e(t.content).css("margin-left",n),t.moveLeftFooter&&e(t.footer).css("margin-left",n)}),o.find("span").toggleClass(t.collapseNavBarClass+" "+t.expandNavBarClass)}),!1})})},e.fn.navigationBar.defaults={handlePopState:!0,fitHeight:!1,content:"#main",footer:"footer",moveLeftContent:!0,moveLeftFooter:!0,menuLeftClass:"glyphicon-menu-right",menuDownClass:"glyphicon-menu-down",collapseNavBarClass:"fa-angle-double-left",expandNavBarClass:"fa-angle-double-right",activeClass:"active",navBarToggleDataAttr:"collapse-side-nav",subMenuNavToggleDataAttr:"collapse-sub-menu"}}(jQuery);
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/partials/deploy.html b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/partials/deploy.html
new file mode 100644
index 0000000000..534d0e90e0
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/main/webapp/partials/deploy.html
@@ -0,0 +1,80 @@
+
+
This is a template for a simple marketing or informational website. It includes a large callout called a
+ jumbotron and three supporting pieces of content. Use it as a starting point to create something more
+ unique.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas sed diam eget risus varius blandit sit amet non
+ magna. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent commodo cursus magna, vel scelerisque
+ nisl consectetur et. Cras mattis consectetur purus sit amet fermentum. Duis mollis, est non commodo luctus, nisi
+ erat porttitor ligula, eget lacinia odio sem nec elit. Aenean lacinia bibendum nulla sed consectetur.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/EmbeddedSolrServerFactory.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/EmbeddedSolrServerFactory.java
new file mode 100644
index 0000000000..2a4b338532
--- /dev/null
+++ 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/EmbeddedSolrServerFactory.java
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+package org.apache.hadoop.yarn.appcatalog.application;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
+import org.apache.solr.client.solrj.request.CoreAdminRequest;
+import org.apache.solr.core.NodeConfig;
+import org.apache.solr.core.SolrResourceLoader;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Embedded solr server factory class for unit tests.
+ */
+public final class EmbeddedSolrServerFactory {
+
+ private EmbeddedSolrServerFactory() {
+ }
+
+ /**
+ * Cleans the given solrHome directory and creates a new EmbeddedSolrServer.
+ *
+ * @param solrHome
+ * the Solr home directory to use
+ * @param configSetHome
+ * the directory containing config sets
+ * @param coreName
+ * the name of the core, must have a matching directory in configHome
+ *
+ * @return an EmbeddedSolrServer with a core created for the given coreName
+ * @throws IOException
+ */
+ public static SolrClient create(final String solrHome,
+ final String configSetHome, final String coreName)
+ throws IOException, SolrServerException {
+ return create(solrHome, configSetHome, coreName, true);
+ }
+
+ /**
+ * @param solrHome
+ * the Solr home directory to use
+ * @param configSetHome
+ * the directory containing config sets
+ * @param coreName
+ * the name of the core, must have a matching directory in configHome
+ * @param cleanSolrHome
+ * if true the directory for solrHome will be deleted and re-created
+ * if it already exists
+ *
+ * @return an EmbeddedSolrServer with a core created for the given coreName
+ * @throws IOException
+ */
+ public static SolrClient create(final String solrHome,
+ final String configSetHome, final String coreName,
+ final boolean cleanSolrHome) throws IOException, SolrServerException {
+
+ final File solrHomeDir = new File(solrHome);
+ if (solrHomeDir.exists()) {
+ FileUtils.deleteDirectory(solrHomeDir);
+ solrHomeDir.mkdirs();
+ } else {
+ solrHomeDir.mkdirs();
+ }
+
+ final SolrResourceLoader loader = new SolrResourceLoader(
+ solrHomeDir.toPath());
+ final Path configSetPath = Paths.get(configSetHome).toAbsolutePath();
+
+ final NodeConfig config = new NodeConfig.NodeConfigBuilder(
+ "embeddedSolrServerNode", loader)
+ .setConfigSetBaseDirectory(configSetPath.toString()).build();
+
+ final EmbeddedSolrServer embeddedSolrServer = new EmbeddedSolrServer(config,
+ coreName);
+
+ final CoreAdminRequest.Create createRequest = new CoreAdminRequest.Create();
+ createRequest.setCoreName(coreName);
+ createRequest.setConfigSet(coreName);
+ embeddedSolrServer.request(createRequest);
+
+ return embeddedSolrServer;
+ }
+
+}
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
new file mode 100644
index 0000000000..a43e0d4121
--- /dev/null
+++ 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
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+package org.apache.hadoop.yarn.appcatalog.application;
+
+import org.apache.hadoop.yarn.appcatalog.model.AppStoreEntry;
+import org.apache.hadoop.yarn.appcatalog.model.Application;
+import org.apache.solr.client.solrj.SolrClient;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.powermock.api.mockito.PowerMockito;
+import static org.powermock.api.mockito.PowerMockito.when;
+import static org.powermock.api.support.membermodification.MemberMatcher.method;
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+/**
+ * Unit test for AppCatalogSolrClient.
+ */
+public class TestAppCatalogSolrClient {
+
+ static final String CONFIGSET_DIR = "src/test/resources/configsets";
+ private static SolrClient solrClient;
+ private static AppCatalogSolrClient spy;
+
+ @Before
+ public void setup() throws Exception {
+ String targetLocation = EmbeddedSolrServerFactory.class
+ .getProtectionDomain().getCodeSource().getLocation().getFile() + "/..";
+
+ String solrHome = targetLocation + "/solr";
+ solrClient = EmbeddedSolrServerFactory.create(solrHome, CONFIGSET_DIR,
+ "exampleCollection");
+ spy = PowerMockito.spy(new AppCatalogSolrClient());
+ when(spy, method(AppCatalogSolrClient.class, "getSolrClient"))
+ .withNoArguments().thenReturn(solrClient);
+ }
+
+ @After
+ public void teardown() throws Exception {
+ try {
+ solrClient.close();
+ } catch (Exception e) {
+ }
+ }
+
+ @Test
+ public void testRegister() throws Exception {
+ Application example = new Application();
+ example.setOrganization("jenkins-ci.org");
+ example.setName("jenkins");
+ example.setDescription("World leading open source automation system.");
+ example.setIcon("/css/img/feather.png");
+ spy.register(example);
+ List apps = spy.getRecommendedApps();
+ assertEquals(1, apps.size());
+ }
+
+ @Test
+ public void testSearch() throws Exception {
+ Application example = new Application();
+ example.setOrganization("jenkins-ci.org");
+ example.setName("jenkins");
+ example.setDescription("World leading open source automation system.");
+ example.setIcon("/css/img/feather.png");
+ spy.register(example);
+ List results = spy.search("name_s:jenkins");
+ int expected = 1;
+ int actual = results.size();
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testNotFoundSearch() throws Exception {
+ Application example = new Application();
+ example.setOrganization("jenkins-ci.org");
+ example.setName("jenkins");
+ example.setDescription("World leading open source automation system.");
+ example.setIcon("/css/img/feather.png");
+ spy.register(example);
+ List results = spy.search("name_s:abc");
+ int expected = 0;
+ int actual = results.size();
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testGetRecommendedApps() throws Exception {
+ List expected = spy.getRecommendedApps();
+ List actual = spy.getRecommendedApps();
+ 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
new file mode 100644
index 0000000000..ca4fba9bbd
--- /dev/null
+++ 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
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+package org.apache.hadoop.yarn.appcatalog.controller;
+
+import org.apache.hadoop.yarn.service.api.records.Service;
+import org.apache.hadoop.yarn.appcatalog.model.AppEntry;
+import org.apache.hadoop.yarn.service.api.records.Component;
+import org.apache.hadoop.yarn.service.api.records.Container;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test for AppDetailsController.
+ */
+public class AppDetailsControllerTest {
+
+ private AppDetailsController controller;
+
+ @Before
+ public void setUp() throws Exception {
+ this.controller = new AppDetailsController();
+
+ }
+
+ @Test
+ public void testGetDetails() throws Exception {
+ String id = "application 1";
+ AppDetailsController ac = Mockito.mock(AppDetailsController.class);
+
+ AppEntry actual = new AppEntry();
+ actual.setName(id);
+ when(ac.getDetails(id)).thenReturn(actual);
+ final AppEntry result = ac.getDetails(id);
+ assertEquals(result, actual);
+ }
+
+ @Test
+ public void testGetStatus() throws Exception {
+ String id = "application 1";
+ AppDetailsController ac = Mockito.mock(AppDetailsController.class);
+
+ Service yarnfile = new Service();
+ Component comp = new Component();
+ Container c = new Container();
+ c.setId("container-1");
+ List containers = new ArrayList();
+ containers.add(c);
+ comp.setContainers(containers);
+ yarnfile.addComponent(comp);
+ AppEntry actual = new AppEntry();
+ actual.setName(id);
+ actual.setYarnfile(yarnfile);
+ when(ac.getStatus(id)).thenReturn(actual);
+ final AppEntry result = ac.getStatus(id);
+ assertEquals(result, actual);
+ }
+
+ @Test
+ public void testStopApp() throws Exception {
+ String id = "application 1";
+ AppDetailsController ac = Mockito.mock(AppDetailsController.class);
+
+ Service yarnfile = new Service();
+ Component comp = new Component();
+ Container c = new Container();
+ c.setId("container-1");
+ List containers = new ArrayList();
+ containers.add(c);
+ comp.setContainers(containers);
+ yarnfile.addComponent(comp);
+ Response expected = Response.ok().build();
+ when(ac.stopApp(id)).thenReturn(Response.ok().build());
+ final Response actual = ac.stopApp(id);
+ assertEquals(expected.getStatus(), actual.getStatus());
+ }
+
+ @Test
+ public void testRestartApp() throws Exception {
+ String id = "application 1";
+ AppDetailsController ac = Mockito.mock(AppDetailsController.class);
+
+ Service yarnfile = new Service();
+ Component comp = new Component();
+ Container c = new Container();
+ c.setId("container-1");
+ List containers = new ArrayList();
+ containers.add(c);
+ comp.setContainers(containers);
+ yarnfile.addComponent(comp);
+ Response expected = Response.ok().build();
+ when(ac.restartApp(id)).thenReturn(Response.ok().build());
+ final Response actual = ac.restartApp(id);
+ assertEquals(expected.getStatus(), actual.getStatus());
+ }
+
+ @Test
+ public void testPathAnnotation() throws Exception {
+ assertNotNull(this.controller.getClass()
+ .getAnnotations());
+ assertThat("The controller has the annotation Path",
+ this.controller.getClass()
+ .isAnnotationPresent(Path.class));
+
+ final Path path = this.controller.getClass()
+ .getAnnotation(Path.class);
+ assertThat("The path is /app_details", path.value(),
+ is("/app_details"));
+ }
+
+}
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/AppListControllerTest.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/AppListControllerTest.java
new file mode 100644
index 0000000000..97f288e247
--- /dev/null
+++ 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/AppListControllerTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+package org.apache.hadoop.yarn.appcatalog.controller;
+
+import org.apache.hadoop.yarn.appcatalog.model.AppEntry;
+import org.apache.hadoop.yarn.service.api.records.Service;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test for AppListController.
+ */
+public class AppListControllerTest {
+
+ private AppListController controller;
+
+ @Before
+ public void setUp() throws Exception {
+ this.controller = new AppListController();
+
+ }
+
+ @Test
+ public void testGetList() throws Exception {
+ AppListController ac = Mockito.mock(AppListController.class);
+
+ List actual = new ArrayList();
+ when(ac.getList()).thenReturn(actual);
+ final List result = ac.getList();
+ assertEquals(result, actual);
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ String id = "application 1";
+ AppListController ac = Mockito.mock(AppListController.class);
+
+ Response expected = Response.ok().build();
+ when(ac.delete(id, id)).thenReturn(Response.ok().build());
+ final Response actual = ac.delete(id, id);
+ assertEquals(expected.getStatus(), actual.getStatus());
+ }
+
+ @Test
+ public void testDeploy() throws Exception {
+ String id = "application 1";
+ AppListController ac = Mockito.mock(AppListController.class);
+ Service service = new Service();
+ Response expected = Response.ok().build();
+ when(ac.deploy(id, service)).thenReturn(Response.ok().build());
+ final Response actual = ac.deploy(id, service);
+ assertEquals(expected.getStatus(), actual.getStatus());
+ }
+
+ @Test
+ public void testPathAnnotation() throws Exception {
+ assertNotNull(this.controller.getClass()
+ .getAnnotations());
+ assertThat("The controller has the annotation Path",
+ this.controller.getClass().isAnnotationPresent(Path.class));
+
+ final Path path = this.controller.getClass()
+ .getAnnotation(Path.class);
+ assertThat("The path is /app_list", path.value(), is("/app_list"));
+ }
+
+}
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/AppStoreControllerTest.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/AppStoreControllerTest.java
new file mode 100644
index 0000000000..d09952b139
--- /dev/null
+++ 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/AppStoreControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+package org.apache.hadoop.yarn.appcatalog.controller;
+
+import org.apache.hadoop.yarn.appcatalog.model.AppStoreEntry;
+import org.apache.hadoop.yarn.appcatalog.model.Application;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for AppStoreController.
+ */
+public class AppStoreControllerTest {
+
+ private AppStoreController controller;
+
+ @Before
+ public void setUp() throws Exception {
+ this.controller = new AppStoreController();
+
+ }
+
+ @Test
+ public void testGetRecommended() throws Exception {
+ AppStoreController ac = Mockito.mock(AppStoreController.class);
+ List actual = new ArrayList();
+ when(ac.get()).thenReturn(actual);
+ final List result = ac.get();
+ assertEquals(result, actual);
+ }
+
+ @Test
+ public void testSearch() throws Exception {
+ String keyword = "jenkins";
+ AppStoreController ac = Mockito.mock(AppStoreController.class);
+ List expected = new ArrayList();
+ when(ac.search(keyword)).thenReturn(expected);
+ final List actual = ac.search(keyword);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testRegister() throws Exception {
+ AppStoreController ac = Mockito.mock(AppStoreController.class);
+ Application app = new Application();
+ app.setName("jenkins");
+ app.setOrganization("jenkins.org");
+ app.setDescription("This is a description");
+ app.setIcon("/css/img/feather.png");
+ Response expected = Response.ok().build();
+ when(ac.register(app)).thenReturn(Response.ok().build());
+ final Response actual = ac.register(app);
+ assertEquals(expected.getStatus(), actual.getStatus());
+ }
+
+ @Test
+ public void testPathAnnotation() throws Exception {
+ assertNotNull(this.controller.getClass()
+ .getAnnotations());
+ assertThat("The controller has the annotation Path",
+ this.controller.getClass()
+ .isAnnotationPresent(Path.class));
+
+ final Path path = this.controller.getClass()
+ .getAnnotation(Path.class);
+ assertThat("The path is /app_store", path.value(), is("/app_store"));
+ }
+
+}
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/javascript/controllersSpec.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/javascript/controllersSpec.js
new file mode 100644
index 0000000000..ec58934240
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/javascript/controllersSpec.js
@@ -0,0 +1,249 @@
+/*
+ * 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.
+ */
+
+describe('Controller tests', function () {
+
+ // Unit test for listing, and start/stop applications.
+ describe('AppListController', function() {
+ var scope, ctrl, http, httpBackend;
+
+ beforeEach(module('app'));
+ beforeEach(inject(function ($controller, $rootScope, $http, $httpBackend) {
+ scope = $rootScope.$new();
+ rootScope = $rootScope;
+ http = $http;
+ httpBackend = $httpBackend;
+ ctrl = $controller('AppListController', {$scope: scope});
+ }));
+
+ afterEach(function() {
+ httpBackend.verifyNoOutstandingExpectation();
+ httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('should contain appList', function () {
+ httpBackend.expectGET('/v1/app_list').respond(200, [{id:"jenkins",name:"jenkins",app:"",yarnfile:{}}]);
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ httpBackend.flush();
+ expect(scope.appList.length).toBe(1);
+ });
+
+ it('should test to delete app', function () {
+ httpBackend.expectGET('/v1/app_list').respond(200, [{id:"jenkins",name:"jenkins",app:"",yarnfile:{}}]);
+ httpBackend.expectDELETE('/v1/app_list/jenkins/jenkins').respond(200, {data:"Application Deleted."});
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ spyOn(rootScope, '$emit');
+ scope.$apply(function() {
+ scope.deleteApp("jenkins","jenkins");
+ });
+ httpBackend.flush();
+ expect(rootScope.$emit).toHaveBeenCalledWith('RefreshAppList', {});
+ });
+
+ it('should test to refresh appList', function() {
+ spyOn(rootScope, '$emit');
+ httpBackend.expectGET('/v1/app_list').respond(200, [{id:"jenkins",name:"jenkins",app:"",yarnfile:{}}]);
+ httpBackend.expectGET('/v1/app_list').respond(200, [{id:"jenkins",name:"jenkins",app:"",yarnfile:{}}]);
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ scope.$apply(function() {
+ scope.refreshList();
+ });
+ httpBackend.flush();
+ expect(rootScope.$emit).toHaveBeenCalledWith('hideLoadScreen', {});
+ })
+ });
+
+ // Unit test for inspect YARN application details.
+ describe('AppDetailsController', function() {
+ var scope, ctrl, http, routeParams, httpBackend;
+
+ beforeEach(module('app'));
+ beforeEach(inject(function ($controller, $rootScope, $http, $routeParams, $httpBackend) {
+ scope = $rootScope.$new();
+ rootScope = $rootScope;
+ http = $http;
+ routeParams = $routeParams;
+ httpBackend = $httpBackend;
+ ctrl = $controller('AppDetailsController', {$scope: scope});
+ }));
+
+ afterEach(function() {
+ httpBackend.verifyNoOutstandingExpectation();
+ httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('should contain unknown state', function () {
+ httpBackend.expectGET('/v1/app_details/config/undefined').respond(200, {"yarnfile":{"state":"UNKNOWN","components":[]}});
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ httpBackend.flush();
+ expect(scope.details.yarnfile.state).toBe("UNKNOWN");
+ });
+
+ it('should run test to refrshed details', function () {
+ httpBackend.expectGET('/v1/app_details/config/undefined').respond(200, {"yarnfile":{"state":"UNKNOWN","components":[]}});
+ httpBackend.expectGET('/v1/app_details/status/aabbccdd').respond(200, {yarnfile:{state: "ACCEPTED", components:[]}});
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ scope.$apply(function() {
+ routeParams.id = "aabbccdd";
+ scope.appName = "aabbccdd";
+ scope.refreshAppDetails();
+ });
+ httpBackend.flush();
+ expect(scope.details.yarnfile.state).toBe("ACCEPTED");
+ });
+
+ it('should run test to restart app', function () {
+ httpBackend.expectGET('/v1/app_details/config/undefined').respond(200, {"yarnfile":{"state":"UNKNOWN","components":[]}});
+ httpBackend.expectPOST('/v1/app_details/restart/aabbccdd').respond(200, {yarnfile:{state: "ACCEPTED", components:[]}});
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ httpBackend.expectGET('/v1/app_details/status/undefined').respond(200, {yarnfile:{state: "ACCEPTED", components:[]}});
+ scope.$apply(function() {
+ scope.restartApp("aabbccdd");
+ });
+ httpBackend.flush();
+ expect(scope.details.yarnfile.components).toBe();
+ });
+
+ it('should run test to stop app', function () {
+ httpBackend.expectGET('/v1/app_details/config/undefined').respond(200, {"yarnfile":{"state":"UNKNOWN","components":[]}});
+ httpBackend.expectPOST('/v1/app_details/stop/aabbccdd').respond(200, {yarnfile:{state: "STOPPED", components:[]}});
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ httpBackend.expectGET('/v1/app_details/status/undefined').respond(200, {yarnfile:{state: "ACCEPTED", components:[]}});
+ scope.$apply(function() {
+ scope.stopApp("aabbccdd");
+ });
+ httpBackend.flush();
+ expect(scope.details.yarnfile.components).toBe();
+ });
+
+ });
+
+ // Unit test for deploying app, and search for apps from Yarn Appstore.
+ describe('AppStoreController', function() {
+ var scope, ctrl, http, httpBackend;
+
+ beforeEach(module('app'));
+ beforeEach(inject(function ($controller, $rootScope, $http, $httpBackend) {
+ scope = $rootScope.$new();
+ http = $http;
+ httpBackend = $httpBackend;
+ ctrl = $controller('AppStoreController', {$scope: scope});
+ }));
+
+ afterEach(function() {
+ httpBackend.verifyNoOutstandingExpectation();
+ httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('should contain appStore', function () {
+ httpBackend.expectGET('/v1/app_store/recommended').respond(200, "");
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ httpBackend.flush();
+ expect(scope.appStore.length).toBe(0);
+ });
+
+ it('should run test to deploy app', function() {
+ httpBackend.expectGET('/v1/app_store/recommended').respond(200, "");
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ httpBackend.flush();
+ scope.$apply(function() {
+ scope.deployApp("aabbccdd");
+ });
+ expect(scope.appStore.length).toBe(0);
+ });
+
+ it('should run test to search for apps', function() {
+ httpBackend.expectGET('/v1/app_store/recommended').respond(200, "");
+ httpBackend.expectGET('/v1/app_store/search?q=aabbccdd').respond(204, {data:'ACCEPTED'});
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ scope.$apply(function() {
+ scope.searchText = "aabbccdd";
+ scope.change("aabbccdd");
+ });
+ httpBackend.flush();
+ expect(scope.appStore.data).toBe('ACCEPTED');
+ });
+
+ });
+
+ // Unit test cases for creating a new YARN application.
+ describe('NewAppController', function() {
+ var scope, ctrl, http, httpBackend;
+
+ beforeEach(module('app'));
+ beforeEach(inject(function ($controller, $rootScope, $http, $httpBackend) {
+ scope = $rootScope.$new();
+ http = $http;
+ httpBackend = $httpBackend;
+ ctrl = $controller('NewAppController', {$scope: scope});
+ }));
+
+ afterEach(function() {
+ httpBackend.verifyNoOutstandingExpectation();
+ httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('should contain details', function () {
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ httpBackend.flush();
+ expect(scope.details.name).toBe("");
+ });
+
+ it('should run test to register data to backend', function() {
+ httpBackend.expectPOST('/v1/app_store/register').respond(204, {data:'ACCEPTED'});
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ scope.$apply(function() {
+ scope.save();
+ });
+ httpBackend.flush();
+ expect(scope.message).toEqual("Application published successfully.");
+ });
+
+ it('should run test to fail register data to backend', function() {
+ httpBackend.expectPOST('/v1/app_store/register').respond(500, {data:'INTERNAL SERVER ERROR'});
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ scope.$apply(function() {
+ scope.save();
+ });
+ httpBackend.flush();
+ expect(scope.error).toEqual("Error in registering application configuration.");
+ });
+
+ it('should run test to add more component to details', function() {
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ expect(scope.details.components.length).toEqual(1);
+ scope.$apply(function() {
+ scope.add();
+ });
+ httpBackend.flush();
+ expect(scope.details.components.length).toEqual(2);
+ });
+
+ it('should run test to remove second component', function() {
+ httpBackend.expectGET('partials/home.html').respond(200, "");
+ expect(scope.details.components.length).toEqual(1);
+ scope.$apply(function() {
+ scope.add();
+ scope.remove(1);
+ });
+ httpBackend.flush();
+ expect(scope.details.components.length).toEqual(1);
+ });
+ });
+
+});
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/javascript/karma.conf.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/javascript/karma.conf.js
new file mode 100644
index 0000000000..f7620a4f93
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/javascript/karma.conf.js
@@ -0,0 +1,34 @@
+module.exports = function(config){
+ config.set({
+
+ basePath : '../../../',
+
+ files : [
+ 'target/generated-sources/vendor/angular**/**.min.js',
+ 'target/generated-sources/vendor/angular-mocks/angular-mocks.js',
+ 'src/main/javascript/**/*.js',
+ 'src/test/javascript/**/*Spec.js',
+ 'src/test/javascript/**/!(karma.conf).js'
+ ],
+
+ autoWatch : true,
+
+ frameworks: ['jasmine'],
+
+ browsers: ['PhantomJS'],
+
+ plugins : [
+ 'karma-chrome-launcher',
+ 'karma-firefox-launcher',
+ 'karma-phantomjs-launcher',
+ 'karma-jasmine',
+ 'karma-junit-reporter'
+ ],
+
+ junitReporter : {
+ outputFile: 'target/test_out/unit.xml',
+ suite: 'src/test/javascript'
+ }
+
+ });
+};
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets.tgz b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets.tgz
new file mode 100644
index 0000000000..3352d31e31
Binary files /dev/null and b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets.tgz differ
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/lang/stopwords_en.txt b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/lang/stopwords_en.txt
new file mode 100644
index 0000000000..2c164c0b2a
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/lang/stopwords_en.txt
@@ -0,0 +1,54 @@
+# 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.
+
+# a couple of test stopwords to test that the words are really being
+# configured from this file:
+stopworda
+stopwordb
+
+# Standard english stop words taken from Lucene's StopAnalyzer
+a
+an
+and
+are
+as
+at
+be
+but
+by
+for
+if
+in
+into
+is
+it
+no
+not
+of
+on
+or
+such
+that
+the
+their
+then
+there
+these
+they
+this
+to
+was
+will
+with
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/params.json b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/params.json
new file mode 100644
index 0000000000..06114ef257
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/params.json
@@ -0,0 +1,20 @@
+{"params":{
+ "query":{
+ "defType":"edismax",
+ "q.alt":"*:*",
+ "rows":"10",
+ "fl":"*,score",
+ "":{"v":0}
+ },
+ "facets":{
+ "facet":"on",
+ "facet.mincount": "1",
+ "":{"v":0}
+ },
+ "velocity":{
+ "wt": "velocity",
+ "v.template":"browse",
+ "v.layout": "layout",
+ "":{"v":0}
+ }
+}}
\ No newline at end of file
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/protwords.txt b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/protwords.txt
new file mode 100644
index 0000000000..4341c05143
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/protwords.txt
@@ -0,0 +1,20 @@
+# 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.
+
+#-----------------------------------------------------------------------
+# Use a protected word file to protect against the stemmer reducing two
+# unrelated words to the same base word.
+
+# Some non-words that normally won't be encountered,
+# just to test that they won't be stemmed.
+dontstems
+zwhacky
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
new file mode 100644
index 0000000000..20acbc92b4
--- /dev/null
+++ 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
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/solrconfig.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/solrconfig.xml
new file mode 100644
index 0000000000..392feec28a
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/solrconfig.xml
@@ -0,0 +1,36 @@
+
+
+
+ 6.2.1
+
+ ${solr.data.dir:}
+
+
+
+
+ single
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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/stopwords.txt b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/stopwords.txt
new file mode 100644
index 0000000000..ae1e83eeb3
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/stopwords.txt
@@ -0,0 +1,14 @@
+# 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.
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/synonyms.txt b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/synonyms.txt
new file mode 100644
index 0000000000..0ef0e8daab
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/configsets/exampleCollection/conf/synonyms.txt
@@ -0,0 +1,28 @@
+# 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.
+
+#-----------------------------------------------------------------------
+#some test synonym mappings unlikely to appear in real input text
+aaafoo => aaabar
+bbbfoo => bbbfoo bbbbar
+cccfoo => cccbar cccbaz
+fooaaa,baraaa,bazaaa
+
+# Some synonym groups specific to this example
+GB,gib,gigabyte,gigabytes
+MB,mib,megabyte,megabytes
+Television, Televisions, TV, TVs
+#notice we use "gib" instead of "GiB" so any WordDelimiterFilter coming
+#after us won't split it into two words.
+
+# Synonym mappings can be used for spelling correction too
+pixima => pixma
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/log4j.properties b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/log4j.properties
new file mode 100644
index 0000000000..eb04743a90
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/hadoop-yarn-applications-catalog-webapp/src/test/resources/log4j.properties
@@ -0,0 +1,11 @@
+log4j.rootLogger = INFO, CATALINA
+
+# Define all the appenders
+log4j.appender.CATALINA = org.apache.log4j.DailyRollingFileAppender
+log4j.appender.CATALINA.File = target/appcatalog.log
+log4j.appender.CATALINA.Append = true
+log4j.appender.CATALINA.Encoding = UTF-8
+# Roll-over the log once per day
+log4j.appender.CATALINA.DatePattern = '.'yyyy-MM-dd'.log'
+log4j.appender.CATALINA.layout = org.apache.log4j.PatternLayout
+log4j.appender.CATALINA.layout.ConversionPattern = %d [%t] %-5p %c- %m%n
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/pom.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/pom.xml
new file mode 100644
index 0000000000..770bf24e64
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-catalog/pom.xml
@@ -0,0 +1,37 @@
+
+
+
+ 4.0.0
+
+ hadoop-yarn-applications
+ org.apache.hadoop
+ 3.3.0-SNAPSHOT
+
+
+ org.apache.hadoop
+ hadoop-yarn-applications-catalog
+ pom
+
+ YARN Application Catalog
+
+ http://hadoop.apache.org
+
+
+ hadoop-yarn-applications-catalog-webapp
+ hadoop-yarn-applications-catalog-docker
+
+
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/client/ApiServiceClient.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/client/ApiServiceClient.java
index 94f03c347e..834bb03fac 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/client/ApiServiceClient.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/client/ApiServiceClient.java
@@ -77,6 +77,13 @@ public class ApiServiceClient extends AppAdminClient {
private static final Base64 BASE_64_CODEC = new Base64(0);
protected YarnClient yarnClient;
+ public ApiServiceClient() {
+ }
+
+ public ApiServiceClient(Configuration c) throws Exception {
+ serviceInit(c);
+ }
+
@Override protected void serviceInit(Configuration configuration)
throws Exception {
yarnClient = YarnClient.createYarnClient();
@@ -151,7 +158,7 @@ public class ApiServiceClient extends AppAdminClient {
* @return URI to API Service
* @throws IOException
*/
- protected String getServicePath(String appName) throws IOException {
+ public String getServicePath(String appName) throws IOException {
String url = getRMWebAddress();
StringBuilder api = new StringBuilder();
api.append(url)
@@ -215,7 +222,7 @@ public class ApiServiceClient extends AppAdminClient {
}
}
- private Builder getApiClient() throws IOException {
+ public Builder getApiClient() throws IOException {
return getApiClient(getServicePath(null));
}
@@ -226,7 +233,7 @@ public class ApiServiceClient extends AppAdminClient {
* @return
* @throws IOException
*/
- private Builder getApiClient(String requestPath)
+ public Builder getApiClient(String requestPath)
throws IOException {
Client client = Client.create(getClientConfig());
client.setChunkedEncodingSize(null);
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-core/examples/appcatalog/appcatalog.json b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-core/examples/appcatalog/appcatalog.json
new file mode 100755
index 0000000000..6a5f2f36a6
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-core/examples/appcatalog/appcatalog.json
@@ -0,0 +1,28 @@
+{
+ "name": "appcatalog",
+ "version": "1",
+ "components" :
+ [
+ {
+ "name": "catalog",
+ "number_of_containers": 1,
+ "artifact": {
+ "id": "apache/hadoop-yarn-applications-catalog-docker:3.3.0-SNAPSHOT",
+ "type": "DOCKER"
+ },
+ "resource": {
+ "cpus": 1,
+ "memory": "2048"
+ },
+ "configuration": {
+ "env": {
+ "YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE":"true",
+ "YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS":"/etc/hadoop/conf:/etc/hadoop/conf:ro,/var/lib/sss/pipes:/var/lib/sss/pipes:rw",
+ "JAVA_HOME":"/usr/lib/jvm/jre-1.8.0"
+ },
+ "properties": {
+ }
+ }
+ }
+ ]
+}
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/pom.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/pom.xml
index 61ca77a059..78b709a45a 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/pom.xml
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/pom.xml
@@ -37,6 +37,7 @@
hadoop-yarn-applications-distributedshellhadoop-yarn-applications-unmanaged-am-launcherhadoop-yarn-services
+ hadoop-yarn-applications-catalog
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/yarn-service/Examples.md b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/yarn-service/Examples.md
index da7a9c434a..e0d1c01638 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/yarn-service/Examples.md
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/yarn-service/Examples.md
@@ -165,6 +165,19 @@ where `service-name` is optional. If omitted, it uses the name defined in the `Y
Look up your IPs at the RM REST endpoint `http://:8088/app/v1/services/httpd-service`.
Then visit port 8080 for each IP to view the pages.
+## Application Catalog - appcatalog
+
+Application Catalog introduces many exciting new features for deploying Hadoop software that benefit both administrators and end users. With Application Catalog, user gets a personalized view of the software status in Hadoop. In addition, users can install or register applications by using web-based user interface.
+
+To start Application Catalog service with the command:
+```
+yarn app -launch appcatalog
+```
+where `service-name` is user defined name.
+
+The deployment progress of the application catalog is located in Resource Manager UI. When the service reaches STABLE state, application catalog UI is available at:
+http://appcatalog.${SERVICE_NAME}.${USER}.${DOMAIN}:8080/
+
## Docker image ENTRYPOINT support
Docker images may have built with ENTRYPOINT to enable start up of docker image without any parameters.