diff --git a/app/hydrator/main.js b/app/hydrator/main.js index e2ee9005798..9e5799831b7 100644 --- a/app/hydrator/main.js +++ b/app/hydrator/main.js @@ -201,37 +201,12 @@ angular window.CaskCommon.StatusFactory.startPollingForBackendStatus(); }) - .run(function (MYSOCKET_EVENT, myAlert, EventPipe) { - - EventPipe.on(MYSOCKET_EVENT.message, function (data) { - if (data.statusCode > 399 && !data.resource.suppressErrors) { - myAlert({ - title: data.statusCode.toString(), - content: data.response || 'Server had an issue, please try refreshing the page', - type: 'danger' - }); - } - - // The user doesn't need to know that the backend node - // is unable to connect to CDAP. Error messages add no - // more value than the pop showing that the FE is waiting - // for system to come back up. Most of the issues are with - // connect, other than that pass everything else to user. - if (data.warning && data.error.syscall !== 'connect') { - myAlert({ - content: data.warning, - type: 'warning' - }); - } - }); - }) - /** * BodyCtrl * attached to the tag, mostly responsible for * setting the className based events from $state and caskTheme */ - .controller('BodyCtrl', function ($scope, $cookies, $cookieStore, caskTheme, CASK_THEME_EVENT, $rootScope, $state, $log, MYSOCKET_EVENT, MyCDAPDataSource, MY_CONFIG, MYAUTH_EVENT, EventPipe, myAuth, $window, myAlertOnValium, myLoadingService, myHelpers, $http) { + .controller('BodyCtrl', function ($scope, $cookies, $cookieStore, caskTheme, CASK_THEME_EVENT, $rootScope, $state, $log, MY_CONFIG, MYAUTH_EVENT, EventPipe, myAuth, $window, myAlertOnValium, myLoadingService, myHelpers, $http) { window.CaskCommon.CDAPHelpers.setupExperiments(); var activeThemeClass = caskTheme.getClassName(); getVersion(); diff --git a/app/hydrator/routes.js b/app/hydrator/routes.js index b35c37c7153..27c3458c691 100644 --- a/app/hydrator/routes.js +++ b/app/hydrator/routes.js @@ -289,10 +289,11 @@ angular.module(PKG.name + '.feature.hydrator') }); return defer.promise; }, - rVersion: function($state, MyCDAPDataSource) { - var dataSource = new MyCDAPDataSource(); - return dataSource.request({ + rVersion: function($state, $http, myCdapUrl) { + return $http.get(myCdapUrl.constructUrl({ _cdapPath: '/version' + })).then(function(res) { + return res.data; }); } }, diff --git a/app/services/cask-angular-socket-datasource/datasource.js b/app/services/cask-angular-socket-datasource/datasource.js deleted file mode 100644 index 8465009c005..00000000000 --- a/app/services/cask-angular-socket-datasource/datasource.js +++ /dev/null @@ -1,457 +0,0 @@ -/* - * Copyright © 2015-2018 Cask Data, Inc. - * - * 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. - */ - -var socketDataSource = angular.module(PKG.name+'.services'); - - /** - Example Usage: - - MyCDAPDataSource // usage in a controller: - - var dataSrc = new MyCDAPDataSource($scope); - - // polling a namespaced resource example: - dataSrc.poll({ - method: 'GET', - _cdapNsPath: '/foo/bar', - interval: 5000 // in milliseconds. - }, - function(result) { - $scope.foo = result; - } - ); // will poll :/v3/namespaces//foo/bar - - // posting to a systemwide resource: - dataSrc.request({ - method: 'POST', - _cdapPath: '/system/config', - body: { - foo: 'bar' - } - }, - function(result) { - $scope.foo = result; - } - ); // will post to :/v3/system/config - - */ - - socketDataSource.factory('uuid', function ($window) { - return $window.uuid; - }); - - - socketDataSource.provider('MyDataSource', function () { - - this.defaultPollInterval = 10; - - this.$get = function($rootScope, caskWindowManager, mySocket, MYSOCKET_EVENT, $q, MyPromise, uuid, EventPipe) { - var CDAP_API_VERSION = 'v3'; - // FIXME (CDAP-14836): Right now this is scattered across node and client. Need to consolidate this. - const REQUEST_ORIGIN_ROUTER = 'ROUTER'; - - var instances = {}; // keyed by scopeid - - - function DataSource (scope) { - scope = scope || $rootScope.$new(); - - var id = scope.$id, - self = this; - - if(instances[id]) { - // Reuse the same instance if already created. - return instances[id]; - } - - if (!(this instanceof DataSource)) { - return new DataSource(scope); - } - instances[id] = self; - - this.scopeId = id; - this.bindings = {}; - - EventPipe.on(MYSOCKET_EVENT.message, function (data) { - var hash; - var isPoll; - hash = data.resource.id; - - if (data.statusCode>299 || data.warning) { - if (self.bindings[hash]) { - if (self.bindings[hash].errorCallback) { - $rootScope.$apply(self.bindings[hash].errorCallback.bind(null, data.error || data.response)); - } else if (self.bindings[hash].reject) { - $rootScope.$apply(self.bindings[hash].reject.bind(null, {data: data.error || data.response, statusCode: data.statusCode })); - } - } - } else if (self.bindings[hash]) { - if (self.bindings[hash].callback) { - data.response = data.response || {}; - data.response.__pollId__ = hash; - scope.$apply(self.bindings[hash].callback.bind(null, data.response)); - } else if (self.bindings[hash].resolve) { - // https://github.com/angular/angular.js/wiki/When-to-use-$scope.$apply%28%29 - scope.$apply(self.bindings[hash].resolve.bind(null, {data: data.response, id: hash, statusCode: data.statusCode })); - } - /* - At first glance this condition check might be redundant with line 157, - however in the resolve or callback function if the user initiates a stop-poll call then - the execution goes to stopPoll function in line 264 and there we delete the entry from bindings - as we no longer need it. After the stopPoll request has gone out the execution continues back - here and we can do self.bindings[hash].poll as self.bindings[hash] is already deleted in stopPoll. - */ - if (!self.bindings[hash]) { - return; - } - isPoll = self.bindings[hash].poll; - if (!isPoll) { - // We can remove the entry from the self bindings if its not a poll. - // Is not going to be used for anything else. - delete self.bindings[hash]; - } else { - if (self.bindings[hash] && self.bindings[hash].type === 'POLL') { - self.bindings[hash].resource.interval = startClientPoll(hash, self.bindings, self.bindings[hash].resource.intervalTime); - } - } - } - return; - }); - - EventPipe.on(MYSOCKET_EVENT.reconnected, () => { - Object.keys(this.bindings).forEach((reqId) => { - const req = self.bindings[reqId]; - - if (req.poll) { - pausePoll(self.bindings); - } - mySocket.send({ - action: 'request', - resource: req.resource, - }); - }); - }); - - EventPipe.on(MYSOCKET_EVENT.closed, () => { - pausePoll(self.bindings); - }); - - scope.$on('$destroy', function () { - Object.keys(self.bindings).forEach(function(key) { - var b = self.bindings[key]; - if (b.poll) { - stopPoll(self.bindings, b.resource.id); - } - }); - - delete instances[self.scopeId]; - }); - - scope.$on(caskWindowManager.event.blur, function () { - pausePoll(self.bindings); - }); - - scope.$on(caskWindowManager.event.focus, function () { - resumePoll(self.bindings); - }); - - } - - function startClientPoll(resourceId, bindings, interval) { - const intervalTimer = setTimeout(() => { - const resource = bindings[resourceId]? bindings[resourceId].resource : undefined; - if (!resource) { - clearTimeout(intervalTimer); - return; - } - mySocket.send({ - action: 'request', - resource - }); - }, interval); - return intervalTimer; - } - - function stopPoll(bindings, resourceId) { - let id; - if (typeof resourceId === 'object' && resourceId !== null) { - id = resourceId.params.pollId; - } else { - id = resourceId; - } - - if (bindings[id]) { - clearTimeout(bindings[id].resource.interval); - delete bindings[id]; - } - } - - function pausePoll(bindings) { - Object.keys(bindings) - .filter(resourceId => bindings[resourceId].type === 'POLL') - .forEach(resourceId => { - clearTimeout(bindings[resourceId].resource.interval); - }); - } - - function resumePoll(bindings) { - Object.keys(bindings) - .filter(resourceId => bindings[resourceId].type === 'POLL') - .forEach(resourceId => { - bindings[resourceId].resource.interval = startClientPoll(resourceId, bindings, bindings[resourceId].resource); - }); - } - - /** - * Start polling of a resource when in scope. - */ - DataSource.prototype.poll = function (resource, cb, errorCb) { - var self = this; - var generatedResource = {}; - const intervalTime = resource.interval || (resource.options && resource.options.interval) || $rootScope.defaultPollInterval; - var promise = new MyPromise(function(resolve, reject) { - const resourceId = uuid.v4(); - generatedResource = { - id: resourceId, - json: resource.json, - intervalTime, - interval: startClientPoll(resourceId, self.bindings, intervalTime), - body: resource.body, - method: resource.method || 'GET', - suppressErrors: resource.suppressErrors || false - }; - - if (resource.headers) { - generatedResource.headers = resource.headers; - } - - let apiVersion = resource.apiVersion || CDAP_API_VERSION; - if (!resource.requestOrigin || resource.requestOrigin === REQUEST_ORIGIN_ROUTER) { - resource.url = `/${apiVersion}${resource.url}`; - } - - if (resource.requestOrigin) { - generatedResource.requestOrigin = resource.requestOrigin; - } else { - generatedResource.requestOrigin = REQUEST_ORIGIN_ROUTER; - } - - generatedResource.url = buildUrl(resource.url, resource.params || {}); - self.bindings[generatedResource.id] = { - poll: true, - type: 'POLL', - callback: cb, - resource: generatedResource, - errorCallback: errorCb, - resolve: resolve, - reject: reject - }; - - mySocket.send({ - action: 'request', - resource: generatedResource - }); - }, true); - - if (!resource.$isResource) { - promise = promise.then(function(res) { - res = res.data; - res.__pollId__ = generatedResource.id; - return $q.when(res); - }); - } - promise.__pollId__ = generatedResource.id; - return promise; - }; - - /** - * Stop polling of a resource when requested. - * (when scope is destroyed Line 196 takes care of deleting the polling resource) - */ - DataSource.prototype.stopPoll = function(resourceId) { - // Duck Typing for angular's $resource. - var defer = $q.defer(); - var id, resource; - if (angular.isObject(resourceId)) { - id = resourceId.params.pollId; - } else { - id = resourceId; - } - - var match = this.bindings[resourceId]; - - if (match) { - resource = match.resource; - stopPoll(this.bindings, resourceId); - defer.resolve({}); - } else { - defer.reject({}); - } - return defer.promise; - }; - - /** - * Fetch a template configuration on-demand. Send the action - * 'template-config' to the node backend. - */ - DataSource.prototype.config = function (resource, cb, errorCb) { - var deferred = $q.defer(); - - resource.suppressErrors = true; - resource.id = uuid.v4(); - this.bindings[resource.id] = { - resource: resource, - callback: function (result) { - if (cb) { - cb.apply(null, result); - } - deferred.resolve(result); - }, - errorCallback: function(err) { - if (errorCb) { - errorCb.apply(null, err); - } - deferred.reject(err); - } - }; - - mySocket.send({ - action: resource.actionName, - resource: resource - }); - return deferred.promise; - }; - - /** - * Fetch a resource on-demand. Send the action 'request' to - * the node backend. - */ - DataSource.prototype.request = function (resource, cb, errorCb) { - var self = this; - var promise = new MyPromise(function(resolve, reject) { - - var generatedResource = { - json: resource.json, - method: resource.method || 'GET', - suppressErrors: resource.suppressErrors || false - }; - if (resource.body) { - generatedResource.body = resource.body; - } - - if (resource.data) { - generatedResource.body = resource.data; - } - - if (resource.headers) { - generatedResource.headers = resource.headers; - } - if (resource.contentType) { - generatedResource.headers['Content-Type'] = resource.contentType; - } - - let apiVersion = resource.apiVersion || CDAP_API_VERSION; - if (!resource.requestOrigin || resource.requestOrigin === REQUEST_ORIGIN_ROUTER) { - resource.url = `/${apiVersion}${resource.url}`; - } - if (resource.requestOrigin) { - generatedResource.requestOrigin = resource.requestOrigin; - } else { - generatedResource.requestOrigin = REQUEST_ORIGIN_ROUTER; - } - generatedResource.url = buildUrl(resource.url, resource.params || {}); - generatedResource.id = uuid.v4(); - self.bindings[generatedResource.id] = { - type: 'REQUEST', - callback: cb, - errorCallback: errorCb, - resource: generatedResource, - resolve: resolve, - reject: reject - }; - - mySocket.send({ - action: 'request', - resource: generatedResource - }); - }, false); - - if (!resource.$isResource) { - promise = promise.then(function(res) { - res = res.data; - return $q.when(res); - }); - } - - return promise; - }; - - return DataSource; - - }; - - - }); - -// Lifted from $http as a helper method to parse '@params' in the url for $resource. -function buildUrl(url, params) { - if (!params) { - return url; - } - var parts = []; - - function forEachSorted(obj, iterator, context) { - var keys = Object.keys(params).sort(); - for (var i = 0; i < keys.length; i++) { - iterator.call(context, obj[keys[i]], keys[i]); - } - return keys; - } - - function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%3B/gi, ';'). - replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); - } - - forEachSorted(params, function(value, key) { - if (value === null || angular.isUndefined(value)) { - return; - } - if (!angular.isArray(value)) { - value = [value]; - } - - angular.forEach(value, function(v) { - if (angular.isObject(v)) { - if (angular.isDate(v)) { - v = v.toISOString(); - } else { - v = angular.toJson(v); - } - } - parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(v)); - }); - }); - if (parts.length > 0) { - url += ((url.indexOf('?') === -1) ? '?' : '&') + parts.join('&'); - } - return url; -} diff --git a/app/services/cask-angular-socket-datasource/socket.js b/app/services/cask-angular-socket-datasource/socket.js deleted file mode 100644 index ca1a3c1a925..00000000000 --- a/app/services/cask-angular-socket-datasource/socket.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright © 2015-2018 Cask Data, Inc. - * - * 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. - */ - -angular.module(PKG.name+'.services') - -.factory('SockJS', function ($window) { - return $window.SockJS; -}) - -.constant('MYSOCKET_EVENT', { - message: 'mysocket-message', - closed: 'mysocket-closed', - reconnected: 'mysocket-reconnected' -}) - -.provider('mySocket', function () { - - this.prefix = '/_sock'; - - this.$get = function (MYSOCKET_EVENT, SockJS, $log, EventPipe) { - - var self = this, - socket = null, - buffer = [], - firstTime = true; - - function init (attempt) { - $log.log('[mySocket] init'); - - attempt = attempt || 1; - socket = new SockJS(self.prefix); - - socket.onmessage = function (event) { - try { - var data = JSON.parse(event.data); - $log.debug('[mySocket] ←', data); - EventPipe.emit(MYSOCKET_EVENT.message, data); - } - catch(e) { - $log.error(e); - } - }; - - socket.onopen = function () { - if (!firstTime) { - window.CaskCommon.SessionTokenStore.fetchSessionToken().then(() => { - EventPipe.emit(MYSOCKET_EVENT.reconnected); - attempt = 1; - }, () => { - console.log('Failed to fetch session token'); - }); - } - firstTime = false; - - $log.info('[mySocket] opened'); - angular.forEach(buffer, send); - buffer = []; - }; - - socket.onclose = function (event) { - $log.error(event.reason); - EventPipe.emit('backendDown', 'User interface service is down'); - - if(attempt<2) { - EventPipe.emit(MYSOCKET_EVENT.closed, event); - } - - // reconnect with exponential backoff - var d = Math.max(500, Math.round( - (Math.random() + 1) * 500 * Math.pow(2, attempt) - )); - $log.log('[mySocket] will try again in ',d+'ms'); - setTimeout(function () { - init(attempt+1); - }, d); - }; - - } - - function send(obj) { - if(!socket.readyState) { - buffer.push(obj); - return false; - } - - doSend(obj); - - return true; - } - - function doSend(obj) { - var msg = obj, - r = obj.resource; - - if(r) { - msg.resource = r; - - // Majority of the time, we send data as json and expect a json response, but not always (i.e. stream ingest). - // Default to json content-type. - if (msg.resource.json === undefined) { - msg.resource.json = true; - } - - if (!r.method) { - msg.resource.method = 'GET'; - } - - $log.debug('[mySocket] →', msg.action, r.method, r.url); - } - msg.sessionToken = window.CaskCommon.SessionTokenStore.default.getState(); - - socket.send(JSON.stringify(msg)); - } - - init(); - - return { - init: init, - send: send, - close: function () { - return socket.close.apply(socket, arguments); - } - }; - }; - -}); diff --git a/app/services/dashboard/dashboardhelper.js b/app/services/dashboard/dashboardhelper.js deleted file mode 100644 index c90a9d31806..00000000000 --- a/app/services/dashboard/dashboardhelper.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright © 2015 Cask Data, Inc. - * - * 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. - */ - -angular.module(PKG.name + '.services') - .factory('DashboardHelper', function (MyCDAPDataSource, MyChartHelpers, MyMetricsQueryHelper) { - var dataSrc = new MyCDAPDataSource(); - - function startPolling (widget) { - widget.pollId = dataSrc.poll({ - _cdapPath: '/metrics/query', - method: 'POST', - interval: widget.settings.interval, - body: MyMetricsQueryHelper.constructQuery( - 'qid', - MyMetricsQueryHelper.contextToTags(widget.metric.context), - widget.metric - ) - }, function (res) { - - widget.formattedData = formatData(res, widget); - }).__pollId__; - } - - - function stopPolling (widget) { - dataSrc.stopPoll(widget.pollId); - } - - - function startPollDashboard (dashboard) { - angular.forEach(dashboard.columns, function (widget) { - startPolling(widget); - }); - } - - function stopPollDashboard (dashboard) { - angular.forEach(dashboard.columns, function (widget) { - stopPolling(widget); - }); - } - - function fetchData (widget) { - return dataSrc.request({ - _cdapPath: '/metrics/query', - method: 'POST', - body: MyMetricsQueryHelper.constructQuery( - 'qid', - MyMetricsQueryHelper.contextToTags(widget.metric.context), - widget.metric - ) - }) - .then(function (res) { - widget.formattedData = formatData(res, widget); - }); - } - - function pollData (widget) { - return dataSrc.poll({ - _cdapPath: '/metrics/query', - method: 'POST', - body: MyMetricsQueryHelper.constructQuery( - 'qid', - MyMetricsQueryHelper.contextToTags(widget.metric.context), - widget.metric - ) - }) - .then(function (res) { - widget.formattedData = formatData(res, widget); - }); - } - - function fetchDataDashboard (dashboard) { - angular.forEach(dashboard.columns, function (widget) { - fetchData(widget); - }); - } - - function formatData (res, widget) { - var processedData = MyChartHelpers.processData( - res, - 'qid', - widget.metric.names, - widget.metric.resolution, - widget.settings.aggregate - ); - - processedData = MyChartHelpers.c3ifyData(processedData, widget.metric, widget.metricAlias); - var data = { - x: 'x', - columns: processedData.columns, - keys: { - x: 'x' - } - }; - - return data; - } - - return { - startPolling: startPolling, - stopPolling: stopPolling, - startPollDashboard: startPollDashboard, - stopPollDashboard: stopPollDashboard, - fetchData: fetchData, - pollData: pollData, - fetchDataDashboard: fetchDataDashboard - }; - - }); diff --git a/app/services/data/my-cdap-datasource.js b/app/services/data/my-cdap-datasource.js deleted file mode 100644 index 92da5124247..00000000000 --- a/app/services/data/my-cdap-datasource.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright © 2015 Cask Data, Inc. - * - * 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. - */ -angular.module(PKG.name + '.services') - .factory('MyCDAPDataSource', function(MyDataSource, $rootScope, myCdapUrl, $cookies) { - function MyCDAPDataSource(scope) { - scope = scope || $rootScope.$new(); - - if (!(this instanceof MyCDAPDataSource)) { - return new MyCDAPDataSource(scope); - } - - this.MyDataSource = new MyDataSource(scope); - } - - MyCDAPDataSource.prototype.poll = function (resource, cb, errorCb) { - resource.headers = {}; - - if (!resource.url) { - resource.url = myCdapUrl.constructUrl(resource); - } - - return this.MyDataSource.poll(resource, cb, errorCb); - }; - - MyCDAPDataSource.prototype.stopPoll = function (resourceId) { - return this.MyDataSource.stopPoll(resourceId); - }; - - MyCDAPDataSource.prototype.config = function(resource, cb, errorCb) { - resource.actionName = 'template-config'; - return this.MyDataSource.config(resource, cb, errorCb); - }; - - MyCDAPDataSource.prototype.request = function(resource, cb, errorCb) { - resource.headers = {}; - - if (!resource.url) { - resource.url = myCdapUrl.constructUrl(resource); - } - - return this.MyDataSource.request(resource, cb, errorCb); - }; - - return MyCDAPDataSource; - - }); diff --git a/app/services/namespace.js b/app/services/namespace.js index 531aa0d8239..6098dab7319 100644 --- a/app/services/namespace.js +++ b/app/services/namespace.js @@ -15,7 +15,7 @@ */ angular.module(PKG.name + '.services') - .service('myNamespace', function myNamespace($q, MyCDAPDataSource, EventPipe, $http, $rootScope, myAuth, myHelpers, $state) { + .service('myNamespace', function myNamespace($q, EventPipe, $http, $rootScope, myAuth, myHelpers, $state) { this.namespaceList = []; var prom, diff --git a/app/services/settings.js b/app/services/settings.js index 79075f1ebd3..4b697c08cc7 100644 --- a/app/services/settings.js +++ b/app/services/settings.js @@ -20,9 +20,7 @@ angular.module(PKG.name + '.services') return new MyPersistentStorage('user'); }) - .factory('MyPersistentStorage', function MyPersistentStorageFactory($q, MyCDAPDataSource, myHelpers, $rootScope, MYAUTH_EVENT) { - - var data = new MyCDAPDataSource(); + .factory('MyPersistentStorage', function MyPersistentStorageFactory($q, $http, myCdapUrl, myHelpers, $rootScope, MYAUTH_EVENT) { function MyPersistentStorage (type) { this.endpoint = '/configuration/' + type; this.headers = { @@ -54,14 +52,16 @@ angular.module(PKG.name + '.services') if (window.CaskCommon.CDAPHelpers.isAuthSetToManagedMode()) { this.headers['Authorization'] = ($rootScope.currentUser.token ? 'Bearer ' + $rootScope.currentUser.token: null); } - return data.request( + return $http( { method: 'PUT', - _cdapPath: this.endpoint, + url: myCdapUrl.constructUrl({ _cdapPath: this.endpoint }), headers: this.headers, - body: this.data + data: this.data } - ); + ).then(function(res) { + return res.data; + }); }; @@ -97,14 +97,15 @@ angular.module(PKG.name + '.services') this.pending = $q.defer(); - data.request( + $http( { method: 'GET', headers: this.headers, - _cdapPath: this.endpoint - }, + url: myCdapUrl.constructUrl({ _cdapPath: this.endpoint }) + } + ).then( function (res) { - self.data = res.property; + self.data = res.data.property; self.pending.resolve( myHelpers.deepGet(self.data, key, true) ); diff --git a/package.json b/package.json index 5f704835af6..ba4b222f86f 100644 --- a/package.json +++ b/package.json @@ -290,8 +290,6 @@ "selenium-webdriver": "^4.1.1", "serve-favicon": "2.5.0", "shepherd.js": "2.0.0-beta.17", - "sockjs": "0.3.19", - "sockjs-client": "1.4.0", "styled-components": "5.3.1", "svg4everybody": "2.1.9", "typescript": "4.0.5", diff --git a/server.js b/server.js index 659ee3354e5..188eee5cbd2 100644 --- a/server.js +++ b/server.js @@ -14,7 +14,6 @@ * the License. */ -import sockjs from 'sockjs'; import http from 'http'; import fs from 'fs'; import log4js from 'log4js'; @@ -22,7 +21,6 @@ import https from 'https'; import ip from 'ip'; import cookie from 'cookie'; import { getApp } from 'server/express'; -import Aggregator from 'server/aggregator'; import { extractConfig } from 'server/config/parser'; import { getCDAPConfig } from 'server/cdap-config'; import { applyGraphQLMiddleware } from 'gql/graphql'; @@ -32,7 +30,6 @@ import middleware404 from 'server/middleware-404'; var cdapConfig, securityConfig, allowedOrigin = [], - wsConnections = {}, hostname, hostIP = ip.address(); @@ -195,82 +192,9 @@ getCDAPConfig() }) .then(async function(server) { - var sockServer = sockjs.createServer({ - log: function(lvl, msg) { - log.trace(msg); - }, - }); - /** - * Node server now supports Proxy mode. This means, between the client and the node proxy - * there can be another proxy that handle authentication and pass on the user id and auth token. - * This means the client will not know anything about the user but the node proxy and the backend - * will be configured to pass on the auth token and user id from the proxy for authentication. - * - * This is the journey of an auth token and user id in proxy mode. - * - * 1. CDAP starts in k8s which spins up UI in a pod with - * security.authentication.mode: PROXY - * security.authentication.proxy.user.identity.header: x-inverting-proxy-user-id - * 2. Once node proxy goes to PROXY mode, it will get the auth token only for the http - * requests. - * 3. The client will not know about the auth token either. - * 4. Once the client reaches CDAP UI, the proxy would have already authenticated the user. - * 5. The request to upgrade websocket connection should already have the auth token and the user id - * 6. We take those values and add to the connection object (sockjs connection object) - * 7. This then gets picked up at the aggregator module that actually makes the call to the - * backend along with these in the request header. - * 8. Upon receiving the response, we remove these from the request object and send it back - * to the client as if no authentication exists. - */ - let authToken, userid; - sockServer.on('connection', function(c) { - if (!c) { - log.error('Connection requested, but no connection available'); - return; - } - log.debug('[SOCKET OPEN] Connection to client "' + c.id + '" opened'); - // @ts-ignore - var a = new Aggregator(c, { ...cdapConfig, ...securityConfig }); - c.authToken = authToken; - c.userid = userid; - wsConnections[c.id] = c; - c.on('close', function() { - log.debug('Cleaning out aggregator: ' + JSON.stringify(a.connection.id)); - a = null; - c.end(); - c.destroy(); - delete wsConnections[c.id]; - }); - }); - - sockServer.installHandlers(server, { prefix: '/_sock' }); - server.addListener('upgrade', function(req, socket) { - req.headers.authorization = getAuthHeaderFromRawCookies(req); - authToken = req.headers.authorization; - const userIdProperty = cdapConfig['security.authentication.proxy.user.identity.header']; - userid = req.headers[userIdProperty]; - - if (allowedOrigin.indexOf(req.headers.origin) === -1) { - log.info('Unknown Origin: ' + req.headers.origin); - log.info('Denying socket connection and closing the channel'); - socket.end(); - socket.destroy(); - return; - } - }); function gracefulShutdown() { - log.info('Caught SIGTERM. Closing http & ws server'); + log.info('Caught SIGTERM. Closing http server'); server.close(); - if (typeof wsConnections === 'object' && Object.keys(wsConnections).length) { - log.debug(`Closing ${Object.keys(wsConnections).length} open websocket connections`) - Object.values(wsConnections).forEach((connection) => { - log.debug('Ending and destroying all graceful shutdown: ' + connection.readyState); - connection.end(); - connection.destroy(); - }); - log.debug('Closed all open websocket connections'); - wsConnections = {}; - } process.exit(0); } process.on('SIGTERM', gracefulShutdown); diff --git a/server/aggregator.js b/server/aggregator.js deleted file mode 100644 index 90bd1b47a15..00000000000 --- a/server/aggregator.js +++ /dev/null @@ -1,353 +0,0 @@ -// @ts-nocheck -/* - * Copyright © 2015-2020 Cask Data, Inc. - * - * 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. - */ - -import request from 'request'; -import fs from 'fs'; -import log4js from 'log4js'; -import { REQUEST_ORIGIN_ROUTER, REQUEST_ORIGIN_MARKET, constructUrl, deconstructUrl, isVerifiedMarketHost} from 'server/url-helper'; -import * as sessionToken from 'server/token'; -import { stripAuthHeadersInProxyMode } from 'server/express'; -const log = log4js.getLogger('default'); -/** - * Aggregator - * receives resourceObj, aggregate them, - * and send poll responses back through socket - * - * @param {Object} SockJS connection - */ -function Aggregator(conn, cdapConfig) { - // make 'new' optional - if (!(this instanceof Aggregator)) { - return new Aggregator(conn); - } - this.cdapConfig = cdapConfig; - this.connection = conn; - - this.initializeEventListeners(); - this.isSessionValid = false; -} - -Aggregator.prototype.stripAuthHeaderInProxyMode = stripAuthHeadersInProxyMode; - -Aggregator.prototype.initializeEventListeners = function() { - /** - * Handler for data from client via websocket connection - * Checks for the session token in the first message. - * If valid sets the isSessionValid flag to true and proceeds to skip - * validations for subsequent messages. - */ - this.connection.on('data', (message) => { - if (this.isSessionValid) { - return onSocketData.call(this, message); - } - if (!this.validateSession(message)) { - return; - } - this.isSessionValid = true; - onSocketData.call(this, message); - }); - this.connection.on('close', onSocketClose.bind(this)); -}; - -Aggregator.prototype.validateSession = function(message) { - let messageJSON; - try { - /** - * Closes the connection if the session is not valid. - */ - messageJSON = JSON.parse(message); - let authToken = ''; - if ( - messageJSON.resource && - messageJSON.resource.headers && - messageJSON.resource.headers.Authorization - ) { - authToken = messageJSON.resource.headers.Authorization; - } - if (!sessionToken.validateToken(messageJSON.sessionToken, this.cdapConfig, log, authToken)) { - log.error('Found invalid session token. Closing websocket connection'); - this.connection.end(); - onSocketClose.call(this); - return false; - } - } catch (e) { - log.error('Unable to parse message : ' + e); - return false; - } - return true; -}; - -/** - * Pushes the ETL Application configuration for templates and plugins to the - * FE. These configurations are UI specific and hences need to be supported - * here. - */ -Aggregator.prototype.pushConfiguration = function(resource) { - var templateid = resource.templateid; - var pluginid = resource.pluginid; - var configString; - var config = {}; - var statusCode = 404; - var filePaths = []; - var isConfigSemanticsValid; - // Some times there might a plugin that is common across multiple templates - // in which case, this is stored within the common directory. So, if the - // template specific plugin check fails, then attempt to get it from common. - filePaths.push( - __dirname + '/../templates/' + templateid + '/' + pluginid + '.json', - __dirname + '/../templates/common/' + pluginid + '.json' - ); - var i, - paths = filePaths.length; - var fileFound = true; - - // Check if the configuration is present within the plugin for a template - for (i = 0; i < paths; i++) { - try { - configString = fs.readFileSync(filePaths[i], 'utf8'); - statusCode = 200; - fileFound = true; - break; - } catch (e) { - if (e.code === 'ENOENT') { - fileFound = false; - } - } - } - if (!fileFound) { - statusCode = 404; - config = 'NO_JSON_FOUND'; - } else { - try { - config = JSON.parse(configString); - statusCode = 200; - } catch (e) { - statusCode = 500; - config = 'CONFIG_SYNTAX_JSON_ERROR'; - } - } - - if (statusCode === 200 && !(config.metadata && config.metadata['spec-version'])) { - isConfigSemanticsValid = validateSemanticsOfConfigJSON(config); - if (!isConfigSemanticsValid) { - statusCode = 500; - config = 'CONFIG_SEMANTICS_JSON_ERROR'; - } - } - - this.connection.write( - JSON.stringify({ - resource: this.stripAuthHeaderInProxyMode(this.cdapConfig, resource), - statusCode: statusCode, - response: config, - }) - ); -}; - -function validateSemanticsOfConfigJSON(config) { - var groups = config.groups.position; - var groupsMap = config.groups; - var i, j; - var isValid = true; - var fields, fieldsMap; - - for (i = 0; i < groups.length; i++) { - if (!groupsMap[groups[i]] || !isValid) { - isValid = false; - break; - } - - fields = groupsMap[groups[i]].position; - fieldsMap = groupsMap[groups[i]].fields; - - if (!fields || !fieldsMap) { - isValid = false; - } else { - for (j = 0; j < fields.length; j++) { - if (!fieldsMap[fields[j]]) { - isValid = false; - break; - } - } - } - } - - return isValid; -} - -/** - * Helps avoid sending certain properties to the browser (meta attributes used only in the node server) - */ -function stripResource(key, value) { - // note that 'stop' is not the stop timestamp, but rather a stop flag/signal (unlike the startTs) - if (key === 'timerId' || key === 'startTs' || key === 'stop') { - return undefined; - } - return value; -} - -/** - * @private emitResponse - * - * sends data back to the client through socket - * - * @param {object} resource that was requested - * @param {error|null} error - * @param {object} response - * @param {string} body - */ -function emitResponse(resource, error, response, body) { - var timeDiff = Date.now() - resource.startTs; - let authMode = this.cdapConfig['security.authentication.mode']; - /** - * In proxy mode, we stub the 401 response from backend with 500. - * This is because the proxy is still using an auth token that is expired. - * The client (broweser UI) does not understand this and will redirect to login page - * But since security is not enabled from a ui perspective it will again redirect - * to the destination page causing an infinite loop. - * - * This is to break the cycle. This will never happen in an ideal case. This is an - * escape hatch when we go to the worst case. - * @param {*} response - response from backend - */ - const getResponseCode = (response) => { - if (authMode === 'PROXY' && response && response.statusCode === 401) { - return 500; - } - return response && response.statusCode; - }; - if (error) { - log.debug('[ERROR]: (id: ' + resource.id + ', url: ' + resource.url + ')'); - log.trace( - '[ERROR]: (id: ' + - resource.id + - ', url: ' + - resource.url + - ') body : (' + - error.toString() + - ')' - ); - - let newResource = Object.assign({}, resource, { - url: deconstructUrl(this.cdapConfig, resource.url, resource.requestOrigin), - }); - this.connection.write( - JSON.stringify( - { - resource: this.stripAuthHeaderInProxyMode(this.cdapConfig, newResource), - error: error, - warning: error.toString(), - statusCode: getResponseCode(response), - response: response && response.body, - }, - stripResource - ) - ); - } else { - log.debug('[SUCCESS]: (id: ' + resource.id + ', url: ' + resource.url + ')'); - log.trace( - '[' + - timeDiff + - 'ms] Success (' + - resource.id + - ',' + - resource.url + - ') body : (' + - JSON.stringify(body) + - ')' - ); - let newResource = Object.assign({}, resource, { - url: deconstructUrl(this.cdapConfig, resource.url, resource.requestOrigin), - }); - log.debug('[RESPONSE]: (id: ' + newResource.id + ', url: ' + newResource.url + ')'); - this.connection.write( - JSON.stringify( - { - resource: this.stripAuthHeaderInProxyMode(this.cdapConfig, newResource), - statusCode: getResponseCode(response), - response: body, - }, - stripResource - ) - ); - } -} - -/** - * @private onSocketData - * @param {string} message received via socket - */ -function onSocketData(message) { - try { - message = JSON.parse(message); - var r = message.resource; - // early out if market place url is invalid. The server won't attempt to request the specified url. - if (r.requestOrigin === REQUEST_ORIGIN_MARKET && !isVerifiedMarketHost(this.cdapConfig, r.url)) { - log.debug('[REQUEST]: (method: ' + r.method + ', id: ' + r.id + ', url: ' + r.url + ')'); - const invalidMarketRequestError = new Error('invalid market request'); - emitResponse.call(this, r, invalidMarketRequestError, {statusCode: 403, body: invalidMarketRequestError.message}); - log.error('[ERROR]: (url: ' + r.url + ') ' + invalidMarketRequestError.message); - return; - } - r.url = constructUrl( - this.cdapConfig, - r.url, - r.requestOrigin || REQUEST_ORIGIN_ROUTER - ); - switch (message.action) { - case 'template-config': - log.debug( - 'ETL application config request (' + - r.method + - ',' + - r.id + - ',' + - r.templateid + - ',' + - r.pluginid - ); - this.pushConfiguration(r); - break; - case 'request': - r.startTs = Date.now(); - if (!r.requestOrigin || r.requestOrigin === REQUEST_ORIGIN_ROUTER) { - if (!r.headers) { - r.headers = {}; - } - r.headers.Authorization = this.connection.authToken; - r.headers.authorization = this.connection.authToken; - r.headers[this.cdapConfig['security.authentication.proxy.user.identity.header']] = this.connection.userid; - } - log.debug('[REQUEST]: (method: ' + r.method + ', id: ' + r.id + ', url: ' + r.url + ')'); - request(r, emitResponse.bind(this, r)).on('error', function(err) { - log.error('[ERROR]: (url: ' + r.url + ') ' + err.message); - }); - break; - } - } catch (e) { - log.warn(e); - } -} - -/** - * @private onSocketClose - */ -function onSocketClose() { - log.debug('[SOCKET CLOSE] Connection to client "' + this.connection.id + '" closed'); -} - -export default Aggregator;