Skip to content

Commit

Permalink
Fix systemless detection (#245)
Browse files Browse the repository at this point in the history
* fix systemless detection logic on orderer details
* add empty state to orderer details
* add more loading vars
* update node as systemless after sys removal

Signed-off-by: David Huffman <[email protected]>
  • Loading branch information
dshuffma-ibm authored Jul 6, 2022
1 parent f519838 commit de42ae7
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 36 deletions.
2 changes: 1 addition & 1 deletion packages/apollo/src/assets/i18n/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1207,7 +1207,7 @@
"empty_channels_title": "No channels available",
"empty_channels_text": "You can create a channel using an ordering service you have provisioned or imported, or you can join a channel if you have the relevant details.",
"empty_cp_channels_title": "No channels available",
"empty_cp_channels_text": "You can join the ordering node to a channel.",
"empty_cp_channels_text": "Join an orderer to a channel to view channels.",
"empty_identities_title": "No identities available",
"empty_identities_text": "Get started by creating a Certificate Authority and an organization MSP definition. You can then view your organization admin identity here and use it to interact with your network. You can also import identities if you have changed browsers.",
"empty_installed_title": "No installed smart contracts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1424,7 +1424,7 @@ class ChannelModal extends Component {
});

// [PATH 1] - using OSN Admin features in create channel wizard
if (this.props.osnadmin_feats_enabled && orderer && orderer.osnadmin_url) {
if (this.props.osnadmin_feats_enabled && orderer && orderer.osnadmin_url && orderer.systemless) {
this.props.updateState(SCOPE, { use_osnadmin: true }); // change the menu options
this.showStepsInTimeline(['osn_join_channel', 'channel_orderer_organizations']);
this.hideStepsInTimeline(['ordering_service_organization', 'organization_creating_channel']); // but hide these
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import ChannelParticipationModal from './ChannelParticipationModal';
import ChannelParticipationUnjoinModal from './ChannelParticipationUnjoinModal';
import JoinOSNChannelModal from '../JoinOSNChannelModal/JoinOSNChannelModal';
import _ from 'lodash';
import emptyImage from '../../assets/images/empty_channels.svg';

const naturalSort = require('javascript-natural-sort');
const SCOPE = 'ChannelParticipationDetails';
Expand Down Expand Up @@ -144,11 +145,13 @@ class ChannelParticipationDetails extends Component {
(<ItemContainer
containerTitle="channels"
containerTooltip="cp_channels_tooltip"
emptyImage={emptyImage}
emptyTitle="empty_cp_channels_title"
emptyMessage="empty_cp_channels_text"
itemId="channel-list"
id="channel-list-tile"
items={this.props.channelList.channels}
loading={this.props.loading}
listMapping={[
{
header: 'channel',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import SidePanel from '../SidePanel/SidePanel';
import Clipboard from '../../utils/clipboard';
import SidePanelWarning from '../SidePanelWarning/SidePanelWarning';
import { OrdererRestApi } from '../../rest/OrdererRestApi';
import { NodeRestApi } from '../../rest/NodeRestApi';
import ImportantBox from '../ImportantBox/ImportantBox';

const SCOPE = 'ChannelParticipationUnjoinModal';
Expand Down Expand Up @@ -81,6 +82,16 @@ class ChannelParticipationUnjoinModal extends Component {
if (_.get(unjoinResp, 'error') !== undefined) {
this.props.updateState(SCOPE, { error: unjoinResp.error });
} else {

if (this.props.channelInfo.systemChannel) {
try {
// update the orderer as systemless
await NodeRestApi.updateNode({ id: osn.id, systemless: true });
} catch (e) {
console.error(e);
}
}

this.props.onComplete();
this.props.onClose();
}
Expand Down
108 changes: 80 additions & 28 deletions packages/apollo/src/components/OrdererDetails/OrdererDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ class OrdererDetails extends Component {
this.props.clearNotifications(SCOPE);
this.props.updateState(SCOPE, {
loading: true,
channelsLoading: true,
sysChLoading: true,
members: [],
admins: [],
systemChannel: true,
Expand All @@ -125,18 +127,27 @@ class OrdererDetails extends Component {
});
let nodes = await MspRestApi.getAllMsps();
this.props.updateState(SCOPE, { nodes });
await this.getDetails(skipStatusCache);
if (this.channelParticipationEnabled(this.props.details)) {

const ordererDetails = await this.getDetails(skipStatusCache);
if (this.channelParticipationEnabled(ordererDetails)) {
await this.getCPChannelList();
};
this.props.updateState(SCOPE, { loading: false });
this.props.updateState(SCOPE, {
loading: false,
channelsLoading: false,
});
};

// detect if channel participation features should be shown, based on osnadmin_url availability and a feature flag
// detect if channel participation features are enabled (this doesn't mean they should be shown!)
channelParticipationEnabled(obj) {
const has_osnadmin_url = (obj && typeof obj.osnadmin_url === 'string') ? true : false;
const osnadmin_feats_enabled = this.props.feature_flags && this.props.feature_flags.osnadmin_feats_enabled === true;
return osnadmin_feats_enabled && has_osnadmin_url && !this.props.systemChannel;
return osnadmin_feats_enabled && has_osnadmin_url;
}

// detect if channel participation features should be shown, based on osnadmin_url availability and a feature flag && system channel should not exist
isSystemLess(obj) {
return this.channelParticipationEnabled(obj) && !this.props.systemChannel;
}

/* get channel list from channel participation api */
Expand All @@ -146,27 +157,36 @@ class OrdererDetails extends Component {
let channelList = {};

let orderer_tls_identity = await IdentityApi.getTLSIdentity(this.props.selectedNode || this.props.details);
if (orderer_tls_identity) {
if (!orderer_tls_identity) {
// if we don't have a tls identity, we cannot load the system channel details via the channel participation apis...
// so assume we do (or do not) have a system channel based on the "systemless" field
// if we do have the identity it will be more robust to just look up the system channel details via channel participation apis
systemChannel = (this.props.details && this.props.details.systemless) ? false : true; // using the systemless field is our fall back method...
} else {
try {
let all_identity = await IdentityApi.getIdentities();
channelList = await ChannelParticipationApi.mapChannels(all_identity, nodes);
const resp = await ChannelParticipationApi.mapChannels(all_identity, nodes);

// TODO: consolidate error handling
if (_.get(channelList, 'code') === 'ECONNREFUSED') {
if (_.get(resp, 'code') === 'ECONNREFUSED') {
this.props.showError('orderer_not_available_title', SCOPE);
}
if (_.get(channelList, 'code') === 'ECONNRESET') {
if (_.get(resp, 'code') === 'ECONNRESET') {
this.props.showError('orderer_not_available_title', SCOPE);
}
if (_.get(channelList, 'systemChannel.name')) {
await this.getSystemChannelConfig();
channelList.systemChannel.type = 'system_channel';
if (channelList.channels === null) {
channelList.channels = [];

if (resp) {
channelList = resp;
if (_.get(channelList, 'systemChannel.name')) { // system channel does exist
await this.getSystemChannelConfig();
channelList.systemChannel.type = 'system_channel';
if (channelList.channels === null) {
channelList.channels = [];
}
channelList.channels.push(channelList.systemChannel);
} else { // system channel does not exist
systemChannel = false;
}
channelList.channels.push(channelList.systemChannel);
} else {
systemChannel = false;
}
} catch (error) {
Log.error('Unable to get channel list:', error);
Expand Down Expand Up @@ -201,6 +221,7 @@ class OrdererDetails extends Component {
Log.error(error);
}
this.props.updateState(SCOPE, { details: orderer });

this.props.showBreadcrumb('orderer_details_title', { ordererName: orderer.cluster_name }, this.pathname);
this.timestamp = new Date().getTime();
setTimeout(() => {
Expand All @@ -211,6 +232,7 @@ class OrdererDetails extends Component {
}
}, 30000);
this.checkHealth(orderer, skipStatusCache);

if (orderer.raft) {
orderer.raft.forEach(node => {
ComponentApi.getUsageInformation(node)
Expand Down Expand Up @@ -256,6 +278,7 @@ class OrdererDetails extends Component {
this.openNodeDetails(nodeToOpen);
}
}
return orderer;
//this.props.updateState(SCOPE, { loading: false });
}

Expand All @@ -274,7 +297,7 @@ class OrdererDetails extends Component {
if (this.timestamp) {
this.timestamp = 0;
this.props.updateState(SCOPE, { notAvailable: false });
if (!this.channelParticipationEnabled(this.props.details)) {
if (!this.isSystemLess(this.props.details)) {
this.getSystemChannelConfig();
}
}
Expand Down Expand Up @@ -405,16 +428,22 @@ class OrdererDetails extends Component {
members: this.getMsps(first_consortium.groups),
admins: this.getMsps(resp.channel_group.groups.Orderer.groups),
capabilities: this.getCapabilities(resp.channel_group),
loading: false,
sysChLoading: false,
disabled: false,
consenters: l_consenters.map(consenter => this.getConsenterNodeInfo(consenter)),
});
})
.catch(error => {
if (this.isSystemLess(this.props.details)) {
// node's without system channel are expected to fail, don't show a warning.
// ideally this function wouldn't be called, but the timing is tricky and this might get called before we know the configuration by checkHealth()
return null;
}

Log.error(error);
this.props.updateState(SCOPE, {
disabled: true,
loading: false,
sysChLoading: false,
});
if (error.message_key) {
this.props.showError(error.message_key, { nodeName: error.nodeName }, SCOPE);
Expand Down Expand Up @@ -1046,6 +1075,26 @@ class OrdererDetails extends Component {
const groups = [];
let hsm = Helper.getHSMBCCSP(_.get(this.props, 'selectedNode.config_override.General')) === 'PKCS11';
if (!hsm) hsm = Helper.getHSMBCCSP(_.get(this.props, 'selectedNode.config_override[0].General')) === 'PKCS11';

if (this.props.channelsLoading) {
groups.push({
label: 'orderer_type',
value: translate('loading'),
});
} else {
if (!this.props.selectedNode) {
groups.push({
label: 'orderer_type',
value: this.isSystemLess(this.props.details) ? translate('systemless_config') : translate('system_config'),
});
} else {
groups.push({
label: 'orderer_type',
value: this.isSystemLess(this.props.selectedNode) ? translate('systemless_config') : translate('system_config'),
});
}
}

if (this.props.selectedNode) {
groups.push({
label: 'node_location',
Expand Down Expand Up @@ -1216,7 +1265,7 @@ class OrdererDetails extends Component {
text: 'add_orderer_node',
fn: this.openAddOrdererNode,
});
} else if (ActionsHelper.canCreateComponent(this.props.userInfo) && this.channelParticipationEnabled(this.props.details)) {
} else if (ActionsHelper.canCreateComponent(this.props.userInfo) && this.isSystemLess(this.props.details)) {
buttonsOnTheNodesTab.push({
text: 'add_orderer_node',
fn: this.openAddOrdererNode,
Expand Down Expand Up @@ -1395,22 +1444,23 @@ class OrdererDetails extends Component {
<Tab id="ibp-orderer-details"
label={translate('details')}
>
{!this.props.loading && this.channelParticipationEnabled(this.props.details) && !this.props.orderer_tls_identity &&
{!this.props.loading && this.isSystemLess(this.props.details) && !this.props.orderer_tls_identity &&
<div>
<SidePanelWarning title="tls_identity_not_found"
subtitle="orderer_tls_admin_identity_not_found"
/>
</div>
}
{this.channelParticipationEnabled(this.props.details) && this.props.orderer_tls_identity &&
{this.isSystemLess(this.props.details) && this.props.orderer_tls_identity &&
<ChannelParticipationDetails
selectedNode={this.props.selectedNode}
channelList={this.props.channelList}
details={this.props.details}
unJoinComplete={this.getCPChannelList}
loading={this.props.loading}
/>
}
{!hasAssociatedIdentities && (
{!this.props.loading && !hasAssociatedIdentities && (
<div className="ibp-orderer-no-identity">
<p>{translate('orderer_no_identity')}</p>
<Button id="no-identity-button"
Expand All @@ -1420,7 +1470,7 @@ class OrdererDetails extends Component {
</Button>
</div>
)}
{!this.channelParticipationEnabled(this.props.details) && hasAssociatedIdentities && (
{!this.isSystemLess(this.props.details) && hasAssociatedIdentities && (
<div>
{this.renderPendingNotice(translate)}
{this.renderRunningPartial(translate)}
Expand All @@ -1432,7 +1482,7 @@ class OrdererDetails extends Component {
configtxlator_url={this.props.configtxlator_url}
onClose={this.onClose}
ordererId={this.props.match.params.ordererId}
loading={this.props.loading}
loading={this.props.sysChLoading}
disableAddItem={this.props.disabled}
/>
<OrdererMembers
Expand All @@ -1441,7 +1491,7 @@ class OrdererDetails extends Component {
configtxlator_url={this.props.configtxlator_url}
ordererId={this.props.match.params.ordererId}
onClose={this.onClose}
loading={this.props.loading}
loading={this.props.sysChLoading}
disableAddItem={this.props.disabled}
/>
{this.renderConsenters(translate)}
Expand Down Expand Up @@ -1505,7 +1555,7 @@ class OrdererDetails extends Component {
{this.renderUsage(translate)}
</Tab>
)}
{this.channelParticipationEnabled(this.props.selectedNode) && (
{this.isSystemLess(this.props.selectedNode) && (
<Tab
id="ibp-orderer-channels"
label={translate('channels')}
Expand Down Expand Up @@ -1555,6 +1605,8 @@ const dataProps = {
details: PropTypes.object,
history: PropTypes.object,
loading: PropTypes.bool,
channelsLoading: PropTypes.bool,
sysChLoading: PropTypes.bool,
match: PropTypes.object,
members: PropTypes.array,
selected: PropTypes.object,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
.ibp--sticky-section-main-content,
.ibp--sticky-section-callout-group-container {
border-bottom: 1px solid $carbon--cool-gray-100;
min-height: 12rem;
//min-height: 12rem;
padding: 1rem;
}
.ibp--sticky-section-main-content {
Expand Down
9 changes: 7 additions & 2 deletions packages/apollo/src/rest/ChannelParticipationApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,19 @@ export class ChannelParticipationApi {
if (!Array.isArray(osns)) {
osns = [osns];
}
let at_least_one_resp = false;

for (let i in osns) {
const single_resp = await ChannelParticipationApi._getChannels(identities, osns[i]);
if (single_resp && single_resp.channels) {
at_least_one_resp = true;
resp.channels = _.unionWith(resp.channels, single_resp.channels, _.isEqual);
if (resp.systemChannel === null && single_resp.systemChannel !== undefined) {
resp.systemChannel = single_resp.systemChannel;
}
}
}
return resp;
return at_least_one_resp ? resp : null;
}

// --------------------------------------------------------
Expand Down Expand Up @@ -220,6 +223,7 @@ export class ChannelParticipationApi {
};
let last_ch_list_resp = null;
let last_osn = null;
let at_least_one_resp = false;

// iter on each osn in input
for (let i in osns) {
Expand All @@ -230,6 +234,7 @@ export class ChannelParticipationApi {
}

if (resp && Array.isArray(resp.channels)) {
at_least_one_resp = true;

// iter on each channel in channel list
for (let z in resp.channels) {
Expand All @@ -248,7 +253,7 @@ export class ChannelParticipationApi {
ret.systemChannel = await ChannelParticipationApi.map1Channel(identities, last_osn, sys_name);
}

return ret;
return at_least_one_resp ? ret : null;
}

Log.error('unable to create channels map with osnadmin data');
Expand Down
Loading

0 comments on commit de42ae7

Please sign in to comment.