Commit bde512d5 authored by Yann Garcia's avatar Yann Garcia
Browse files

Update Welcome page, Add error message when Network Emulation Stack is not reachable

parent ea3101a0
Loading
Loading
Loading
Loading
+8 −0
Changes for js-apps/frontend/src/js/containers/home/home-page-container.js: 8 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -75,6 +75,14 @@ class HomePageContainer extends Component {
                EdgeNative - Connector provides also a support for Edge Native Connector (3GPP TS 29.222: 3rd Generation Partnership Project; Technical Specification Group Core
                Network and Terminals; Common API Framework for 3GPP Northbound APIs).
              </p>
              <p>
                EdgeNative - Connector provides the user with two network simulation mechanisms to reproduce the behaviour of these 4G, 5G and Wi-Fi scenarios.
                The first mechanism is the built-in network characteristics emulation, which applies configurable latency, jitter, packet loss and throughput
                to the connectivity between terminals, points of access and MEC hosts, enabling rapid experimentation without an external radio stack.
                The second mechanism is an external radio network emulator based on Simu5G/SimuLTE, optionally coupled with SUMO/Veins for terminal mobility,
                so that user traffic and Radio Network Information (MEC012) can reflect a simulated radio access network rather than purely configured link parameters.
                In both cases, the ETSI MEC Service APIs remain the same, allowing the user to compare application behaviour under emulated and radio-simulated conditions.
              </p>
              <p>
                Such contextual information can offer significant differential performance for edge based MEC applications.
                EdgeNative - Connector also provides the support of capabilities described by Edge Platform Application Enablement (MEC011) and Device Application Interface (MEC016).
+54 −2
Changes for js-apps/frontend/src/js/containers/sandbox/configuration-pane.js: 54 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ import {

import {
  uiChangeCurrentDialog,
  uiChangeCurrentAlert,
  uiChangeDialogErrorMsg,
  uiSandboxChangeNetworkFileSelected,
  uiSandboxChangeNetworkInfo,
  uiSandboxChangePauseButton,
@@ -65,6 +67,8 @@ import {
  getNetworkInfo
} from '../../util/scenario-utils';

import { isNemUnreachable } from '../../util/nem-error';

const HIGH_VELOCITY_UE = 'hv';
const LOW_VELOCITY_UE = 'lv';
const STATIONARY_UE = 'zv';
@@ -75,10 +79,13 @@ const ACTION_REMOVE = 'remove';
class ConfigPane extends Component {
  constructor(props) {
    super(props);
    this.ignoreNetworkSelectChange = true;
    this.networkSelectArmTimer = null;
  }

  componentDidMount() {
    log.info('componentDidMount: Configuration Pane mounted');
    this.armNetworkSelect();
    if (this.props.scenario !== null && this.props.scenario.name !== NO_SCENARIO_NAME) {
      this.getScenario(this.props.scenario.name, false);
    } else {
@@ -87,7 +94,32 @@ class ConfigPane extends Component {
    }
  }

  componentWillUnmount() {
    if (this.networkSelectArmTimer) {
      clearTimeout(this.networkSelectArmTimer);
      this.networkSelectArmTimer = null;
    }
  }

  /**
   * Ignore Select onChange until options are stable. RMWC Select fires a
   * synthetic change when the scenario list arrives after sign-in.
   */
  armNetworkSelect() {
    this.ignoreNetworkSelectChange = true;
    if (this.networkSelectArmTimer) {
      clearTimeout(this.networkSelectArmTimer);
    }
    this.networkSelectArmTimer = setTimeout(() => {
      this.ignoreNetworkSelectChange = false;
      this.networkSelectArmTimer = null;
    }, 300);
  }

  componentDidUpdate(prevProps) {
    if (prevProps.networkFiles !== this.props.networkFiles) {
      this.armNetworkSelect();
    }
    // Get configured scenario if activated by another browser
    if (this.props.activationInProgressCount === -1 && this.props.terminationInProgressCount === -1) {
      if (this.props.scenario !== null && this.props.scenario.name !== NO_SCENARIO_NAME) {
@@ -177,10 +209,19 @@ class ConfigPane extends Component {
   * @callback module:api/ScenarioExecutionApi~activateScenarioCallback
   * @param {String} error Error message, if any.
   */
  activateScenarioCb(error/*, data*/) {
  activateScenarioCb(error, _data, response) {
    if (error) {
      this.props.changeActivationInProgressCount(-1);
      this.props.changeActivationInProgressScenarioName('');
      this.resetConfigPane();
      this.resetApiPane();
      if (isNemUnreachable(error, response)) {
        this.props.changeCurrentAlert(ALERT_NEM_NOT_RUNNING);
      }
      return;
    }
    this.props.changeCurrentAlert(null);
    this.props.changeDialogErrorMsg('');
    // Enable movement by default
    this.updateAutomation(false);
  }
@@ -192,7 +233,12 @@ class ConfigPane extends Component {
  }

  setScenario(name) {
    if (!name || name === this.props.networkFileSelected) {
      return;
    }
    log.info(`setScenario: Setting new scenario network: ${name}`);
    this.props.changeCurrentAlert(null);
    this.props.changeDialogErrorMsg('');
    // Reset config & api panes
    this.resetConfigPane();
    this.resetApiPane();
@@ -497,9 +543,12 @@ class ConfigPane extends Component {
              disabled={!networkFileSelectionEnabled}
              options={this.props.networkFiles}
              onChange={event => {
                if (this.ignoreNetworkSelectChange) {
                  return;
                }
                this.setScenario(event.target.value);
              }}
              value={this.props.networkFileSelected}
              value={this.props.networkFileSelected || DEFAULT_NO_NETWORK_FILE_SELECTED}
            />
            {this.props.currentAlert === ALERT_NEM_NOT_RUNNING &&
              <Typography use="subtitle2" style={styles.nemErrorText}>
@@ -771,6 +820,7 @@ const mapStateToProps = state => {
  return {
    page: state.ui.page,
    currentAlert: state.ui.currentAlert,
    dialogErrorMsg: state.ui.dialogErrorMsg,
    networkFiles: state.ui.networkFiles,
    networkFileSelected: state.ui.networkFileSelected,
    networkInfo: state.ui.networkInfo,
@@ -794,6 +844,8 @@ const mapStateToProps = state => {
const mapDispatchToProps = dispatch => {
  return {
    changeCurrentDialog: type => dispatch(uiChangeCurrentDialog(type)),
    changeCurrentAlert: alert => dispatch(uiChangeCurrentAlert(alert)),
    changeDialogErrorMsg: msg => dispatch(uiChangeDialogErrorMsg(msg)),
    changeTerminationInProgressCount: count => dispatch(uiSandboxChangeTerminationInProgressCount(count)),
    changeActivationInProgressCount: count => dispatch(uiSandboxChangeActivationInProgressCount(count)),
    changeActivationInProgressScenarioName: name => dispatch(uiSandboxChangeActivationInProgressScenarioName(name)),
+63 −0
Changes for js-apps/frontend/src/js/util/nem-error.js: 63 added lines, 0 removed lines.
Original line number Diff line number Diff line
import { StatusCodes } from 'http-status-codes';
import { MSG_NEM_NOT_RUNNING } from '../app-constants';

/**
 * HTTP status from a SuperAgent error or response.
 * @param {Object} error
 * @param {Object} response
 * @return {number|undefined}
 */
export function nemHttpStatus(error, response) {
  if (error && typeof error.status === 'number') {
    return error.status;
  }
  if (response && typeof response.status === 'number') {
    return response.status;
  }
  return undefined;
}

/**
 * Best-effort text of an HTTP error body. Empty when SuperAgent did not
 * expose a body (do not invent a "failed to start" fallback).
 * @param {Object} error
 * @param {Object} response
 * @return {string}
 */
export function nemErrorText(error, response) {
  const candidates = [
    response && response.text,
    response && response.body && response.body.error,
    response && typeof response.body === 'string' && response.body,
    error && error.response && error.response.text,
    error && error.response && error.response.body
  ];
  for (let i = 0; i < candidates.length; i++) {
    const v = candidates[i];
    if (typeof v === 'string' && v.trim()) {
      return v.trim();
    }
  }
  return '';
}

/**
 * True when NEM HTTP was not reached. Activate application errors (NEM 500,
 * sudo, INACTIVE) must not match, even if the controller wrapped them as 502.
 * @param {Object} error
 * @param {Object} response
 * @return {boolean}
 */
export function isNemUnreachable(error, response) {
  const status = nemHttpStatus(error, response);
  if (status === StatusCodes.GATEWAY_TIMEOUT ||
      status === StatusCodes.REQUEST_TIMEOUT ||
      status === 0) {
    return true;
  }
  if (status === StatusCodes.BAD_GATEWAY) {
    const text = nemErrorText(error, response);
    return text === MSG_NEM_NOT_RUNNING || text.indexOf(MSG_NEM_NOT_RUNNING) !== -1;
  }
  return false;
}