Skip to content

Commit

Permalink
Add gather version button (#560)
Browse files Browse the repository at this point in the history
* add gather version code
---------
Signed-off-by: David Huffman <[email protected]>
  • Loading branch information
dshuffma-ibm authored Nov 14, 2023
1 parent 9947b1a commit 4509272
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 3 deletions.
4 changes: 3 additions & 1 deletion packages/apollo/src/assets/i18n/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2846,7 +2846,9 @@
"log_detail_desc": "The full details of this activity log are below.",
"log_columns_desc": "Display",
"audit_no_access_msg": "Only users with the \"Manager\" role can view this page.",
"threat_message":"This service instance must be deleted by Oct 31, 2023 to avoid a blockchain network outage",
"threat_message": "This service instance must be deleted by Oct 31, 2023 to avoid a blockchain network outage",
"version_debug_msg": "Version summary",
"version_debug_tooltip": "Use this tool to export a version summary which shows the versions of each of your component. This summary is helpful for debugging/support purposes.",
"hide_archived_channels": "Hide Archived Channels",
"show_archived_channels": "Show Archived Channels"
}
58 changes: 58 additions & 0 deletions packages/apollo/src/components/Settings/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import ToggleSmallSkeleton from 'carbon-components-react/lib/components/ToggleSm
import { NodeRestApi } from '../../rest/NodeRestApi';
import IdentityApi from '../../rest/IdentityApi';
import SidePanel from '../SidePanel/SidePanel';
import { EventsRestApi } from '../../rest/EventsRestApi';

const SCOPE = 'comp_settings';
const Log = new Logger(SCOPE);
Expand Down Expand Up @@ -671,6 +672,62 @@ export class Settings extends Component {
);
}

// show section on version gathering (debug)
renderVersionDebug(translate) {
return (
<div className="ibp-settings-bulk-data-container">
<div className="settings-section">
<h3 className="settings-label">
<BlockchainTooltip direction="right"
triggerText={translate('version_debug_msg')}
>
{translate('version_debug_tooltip')}
</BlockchainTooltip>
</h3>
<div className="settings-button-container">
<Button
id="version_export_button"
disabled={this.props.saving}
onClick={async () => {
try {
const resp = await NodeRestApi.getVersionSummary();
this.downloadVersion(resp);
} catch (e) {
Log.error('error generating version summary', e);
}
}}
>
{translate('generate')}
</Button>
</div>
</div>
</div>
);
}

// download the version summary as a json file
downloadVersion(json) {
let filename = 'versions.' + Date.now() + '.json';
const createTarget = document.body;
let link = document.createElement('a');
if (link.download !== undefined) {
let blob = new Blob([JSON.stringify(json, null, '\t')], { type: 'application/json' });
let url = URL.createObjectURL(blob);
link.setAttribute('download', filename);
link.setAttribute('href', url);
link.style.visibility = 'hidden';
createTarget.appendChild(link);
link.click();
createTarget.removeChild(link);

try {
EventsRestApi.recordActivity({ status: 'success', log: 'generating version summary' });
} catch (e) {
Log.error('unable to record version summary/gathering', e);
}
}
}

render = () => {
const translate = this.props.translate;
const progress_width = isNaN(this.props.width) ? 0 : this.props.width;
Expand Down Expand Up @@ -707,6 +764,7 @@ export class Settings extends Component {
</div>
</div>)}
</div>
{this.renderVersionDebug(translate)}
{this.renderDataManagement(translate)}
{window && window.location && window.location.href && window.location.href.includes('debug') && this.renderDeleteSection(translate)}
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/apollo/src/components/Settings/_settings.scss
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@
margin-bottom: 2rem;
}

.ibp-settings-bulk-data-container {
/*.ibp-settings-bulk-data-container {
padding-top: 1rem;
}
}*/

#ibp-progress-bar-wrap {
width: 19rem;
Expand Down
5 changes: 5 additions & 0 deletions packages/apollo/src/rest/NodeRestApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,11 @@ class NodeRestApi {
static async deleteAllComponents() {
return await RestApi.delete('/saas/api/v3/components/purge');
}

// get version summary on all components
static async getVersionSummary() {
return await RestApi.get('/api/v3/versions');
}
}

export { NodeRestApi, CREATED_COMPONENT_LOCATION, isCreatedComponentLocation };
1 change: 1 addition & 0 deletions packages/apollo/src/utils/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ const Helper = {
createTarget.removeChild(link);
}
},

/*
* Export a nodes as Zip
*/
Expand Down
89 changes: 89 additions & 0 deletions packages/athena/libs/component_lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -1700,6 +1700,95 @@ module.exports = function (logger, ev, t) {
return null;
};

//--------------------------------------------------
// Get the version of a component by trying to reach its fabric version endpoint
//--------------------------------------------------
/*
opts: {
timeout_ms: 0, // [optional] http timeout for asking the component
_max_attempts: 2 // [optional] max number of http reqs to send including orig and retries
}
*/
exports.get_version = (comp_doc, opts, cb) => {
if (!opts) { opts = {}; }
const options = {
method: 'GET',
baseUrl: null,
url: exports.build_version_url(comp_doc),
headers: { 'Accept': 'application/json' },
timeout: !isNaN(opts.timeout_ms) ? Number(opts.timeout_ms) : ev.HTTP_STATUS_TIMEOUT, // give up quickly b/c we don't want status api to hang
rejectUnauthorized: false, // self signed certs are okay
_name: 'ver_req',
_max_attempts: opts._max_attempts || 2,
_retry_codes: { // list of codes we will retry
'429': '429 rate limit exceeded aka too many reqs',
//'408': '408 timeout', // version calls should not retry a timeout, takes too long
}
};

if (options.url === null) { // no url to hit... error out
logger.error('[component] unable to get component version b/c url to use in doc is missing... id:', comp_doc._id);
return cb({
statusCode: 500,
version: '-'
});
} else {
t.misc.retry_req(options, (err, resp) => {
const code = t.ot_misc.get_code(resp);
const body = format_body(resp);
return cb(null, {
statusCode: code,
_body: body,
version_url: options.url,
version: t.misc.prettyPrintVersion(body ? body.Version : null),
});
});
}

// json parse the body if asked for
function format_body(resp) {
let body = null;
if (resp && resp.body) { // parse body to JSON
if (typeof resp.body === 'string') {
try { body = JSON.parse(resp.body); }
catch (e) {
logger.error('[component] unable to format version response as JSON for component id', comp_doc._id, e);
return null;
}
} else {
return resp.body;
}
}
return body;
}
};

//--------------------------------------------------
// build a version url for the component, from a component doc
//--------------------------------------------------
exports.build_version_url = (comp_doc) => {
if (comp_doc) {

// if its been migrated use the legacy routes
if (comp_doc.migrated_from === ev.STR.LOCATION_IBP_SAAS) {
if (comp_doc.type === ev.STR.CA && comp_doc.api_url) {
return comp_doc.api_url_saas + '/version'; // CA's use this route
} else if (comp_doc.operations_url_saas && (comp_doc.type === ev.STR.ORDERER || comp_doc.type === ev.STR.PEER)) {
return comp_doc.operations_url_saas + '/version'; // peers and orderers use this route
}
}

// if it hasn't been migrated use regular routes
if (comp_doc.type === ev.STR.CA && comp_doc.api_url) {
return comp_doc.api_url + '/version'; // CA's use this route
} else if (comp_doc.operations_url && (comp_doc.type === ev.STR.ORDERER || comp_doc.type === ev.STR.PEER)) {
return comp_doc.operations_url + '/version'; // peers and orderers use this route
}
}

return null;
};

//--------------------------------------------------
// Get /cainfo data from all the CAs in the components array
//--------------------------------------------------
Expand Down
20 changes: 20 additions & 0 deletions packages/athena/libs/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -1524,5 +1524,25 @@ module.exports = function (logger, t) {
}
};

// turn version into a 3 part version string
// 'V2_0' -> 'v2.0.0'
// '2.0.0' -> 'v2.0.0'
// 'V1_4_2' -> 'v1.4.2'
exports.prettyPrintVersion = (str) => {
if (typeof str === 'string') {
if (str === 'unknown') { return '-'; }
str = str.trim();
if (str[0].toUpperCase() === 'V') {
str = str.substring(1); // cut off the 'V'
}
const parts = str.includes('_') ? str.split('_') : str.split('.');
while (parts.length < 3) {
parts.push('0');
}
return 'v' + parts.join('.');
}
return '-';
};

return exports;
};
141 changes: 141 additions & 0 deletions packages/athena/libs/other_apis_lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -899,5 +899,146 @@ module.exports = function (logger, ev, t) {
}
};

//-----------------------------------------------------------------------------
// Return a console/component/k8s version summary for support/debug purposes
//-----------------------------------------------------------------------------
exports.version_summary = (req, cb) => {
const console_data = t.ot_misc.parse_versions();
const ret = {
console: {
version: (console_data && console_data.tag) ? t.misc.prettyPrintVersion(console_data.tag) : '-',
commit: (console_data && console_data.athena) ? console_data.athena : '-',
},
components: [],
cluster: {
type: '',
version: '',
go_version: '',
},
operator: {
available_fabric_versions: {}
},
timestamp: Date.now(),
};

t.async.parallel([

// ---- Get component docs from athena db ---- //
(join) => {
t.component_lib.get_all_runnable_components(req, (err, resp) => {
if (err) {
logger.error('[version] unable to get runnable component docs:', err);
return join(null);
} else {

// iter on each component
t.async.eachLimit(resp, 8, (comp_doc, cb_version) => {
const comp = t.comp_fmt.fmt_component_resp(req, comp_doc);

if (!comp_doc || !comp_doc.operations_url) {
ret.components.push({
id: comp.id,
display_name: comp.display_name,
version: '-',
imported: comp.imported,
type: comp.type,
});
return cb_version();
} else {
const options = {
method: 'GET',
baseUrl: null,
url: t.component_lib.build_version_url(comp_doc),
headers: { 'Accept': 'application/json' },
timeout: ev.HTTP_STATUS_TIMEOUT,
rejectUnauthorized: false, // self signed certs are okay
_name: 'status_req',
_max_attempts: 2,
_retry_codes: { // list of codes we will retry
'429': '429 rate limit exceeded aka too many reqs',
}
};
t.misc.retry_req(options, (err_ver, resp_ver) => {
let body = {};
if (resp_ver) {
try {
body = JSON.parse(resp_ver.body);
} catch (e) {
logger.error('[version] unable to parse response from component', comp_doc.id);
}
}
ret.components.push({
id: comp.id,
display_name: comp.display_name,
version: t.misc.prettyPrintVersion(body.Version),
imported: comp.imported,
type: comp.type,
});
return cb_version();
});
}
}, () => {
return join(null, ret);
});
}
});
},

// ---- Get k8s version ---- //
(join) => {
t.deployer.get_k8s_version((err, resp) => {
if (err) {
// error already logged
return join(null);
} else {
ret.cluster.version = (resp && resp._version) ? t.misc.prettyPrintVersion(resp._version) : '-';
ret.cluster.go_version = (resp && resp.goVersion) ? resp.goVersion : '-';
return join(null, resp);
}
});
},

// ---- Get cluster type ---- //
(join) => {
t.deployer.get_cluster_type((err, resp) => {
if (err) {
// error already logged
return join(null);
} else {
ret.cluster.type = (resp && resp.type) ? resp.type : '-';
return join(null, resp);
}
});
},

// ---- Get available fabric versions ---- //
(join) => {
t.deployer.get_fabric_versions(req, (err, resp) => {
if (err) {
// error already logged
join(null);
} else {
const tmp = (resp && resp.versions) ? resp.versions : {};
const types = ['peer', 'orderer', 'ca'];
for (let i in types) {
const fab_type = types[i];
if (tmp && tmp[fab_type]) {
ret.operator.available_fabric_versions[fab_type] = [];
for (let ver in tmp.peer) {
ret.operator.available_fabric_versions[fab_type].push(t.misc.prettyPrintVersion(ver));
}
}
}
join(null, resp.versions);
}
});
}

], (_, results) => {
logger.info('[version] returning version summary');
return cb(null, t.misc.sortItOut(ret));
});
};

return exports;
};
Loading

0 comments on commit 4509272

Please sign in to comment.