blob: 2e631e7cf69be1de83684017779d395366053e32 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="keywords" content=" dev docs">
<title>SPI&#58 Implementation | Eclipse Che Documentation</title>
<link rel="stylesheet" href="css/syntax.css">
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" crossorigin="anonymous">
<!--<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">-->
<link rel="stylesheet" href="css/modern-business.css">
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="css/customstyles.css">
<link rel="stylesheet" href="css/boxshadowproperties.css">
<!-- most color styles are extracted out to here -->
<link rel="stylesheet" href="css/theme-che.css">
<link rel="stylesheet" href="/css/coderay.css" media="screen" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js" crossorigin="anonymous"></script>
<script src="js/jquery.navgoco.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<!-- Anchor.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/anchor-js/2.0.0/anchor.min.js" crossorigin="anonymous"></script>
<script src="js/toc.js"></script>
<script src="js/customscripts.js"></script>
<link rel="shortcut icon" href="che/docs/images/favicon.ico">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<link rel="alternate" type="application/rss+xml" title="che" href="http://0.0.0.0:4000/feed.xml">
<script>
$(document).ready(function() {
// Initialize navgoco with default options
$("#mysidebar").navgoco({
caretHtml: '',
accordion: true,
openClass: 'active', // open
save: false, // leave false or nav highlighting doesn't work right
cookie: {
name: 'navgoco',
expires: false,
path: '/'
},
slide: {
duration: 400,
easing: 'swing'
}
});
$("#collapseAll").click(function(e) {
e.preventDefault();
$("#mysidebar").navgoco('toggle', false);
});
$("#expandAll").click(function(e) {
e.preventDefault();
$("#mysidebar").navgoco('toggle', true);
});
});
</script>
<script>
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
<script>
$(document).ready(function() {
$("#tg-sb-link").click(function() {
$("#tg-sb-sidebar").toggle();
$("#tg-sb-content").toggleClass('col-md-9');
$("#tg-sb-content").toggleClass('col-md-12');
$("#tg-sb-icon").toggleClass('fa-toggle-on');
$("#tg-sb-icon").toggleClass('fa-toggle-off');
});
});
</script>
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-inverse navbar-static-top">
<div class="container topnavlinks">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="fa fa-home fa-lg navbar-brand" href="index.html">&nbsp;<span class="projectTitle"> Eclipse Che Documentation</span></a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<!-- toggle sidebar button -->
<li><a id="tg-sb-link" href="#"><i id="tg-sb-icon" class="fa fa-toggle-on"></i> Nav</a></li>
<!-- entries without drop-downs appear here -->
<li><a href="https://medium.com/eclipse-che-blog/" target="_blank">Blog</a></li>
<li><a href="https://github.com/eclipse/che" target="_blank">Source Code</a></li>
<!-- entries with drop-downs appear here -->
<!-- conditional logic to control which topnav appears for the audience defined in the configuration file.-->
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Get Support<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="https://github.com/eclipse/che/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Akind%2Fbug" target="_blank">Known Bugs</a></li>
<li><a href="https://github.com/eclipse/che/issues/new" target="_blank">File an Issue</a></li>
<li><a href="https://stackoverflow.com/questions/tagged/eclipse-che" target="_blank">Che on StackOverflow</a></li>
</ul>
</li>
<!--
<li>
<a class="email" title="Submit feedback" href="#" onclick="javascript:window.location='mailto:?subject= feedback&body=I have some feedback about the SPI&#58 Implementation page: ' + window.location.href;"><i class="fa fa-envelope-o"></i> Feedback</a>
</li>
-->
<!--comment out this block if you want to hide search-->
<li>
<!--start search-->
<div id="search-demo-container">
<input type="text" id="search-input" placeholder="search...">
<ul id="results-container"></ul>
</div>
<script src="js/jekyll-search.js" type="text/javascript"></script>
<script type="text/javascript">
SimpleJekyllSearch.init({
searchInput: document.getElementById('search-input'),
resultsContainer: document.getElementById('results-container'),
dataSource: 'search.json',
searchResultTemplate: '<li><a href="{url}" title="SPI&amp;#58 Implementation">{title}</a></li>',
noResultsText: 'No results found.',
limit: 10,
fuzzy: true,
})
</script>
<!--end search-->
</li>
</ul>
</div>
</div>
<!-- /.container -->
</nav>
<!-- Page Content -->
<div class="container">
<div id="main">
<!-- Content Row -->
<div class="row">
<!-- Sidebar Column -->
<div class="col-md-3" id="tg-sb-sidebar">
<ul id="mysidebar" class="nav">
<li class="sidebarTitle"> </li>
<li>
<a href="#">Overview</a>
<ul>
<li><a href="index.html">Introduction</a></li>
<li><a href="quick-start.html">Getting Started</a></li>
<li><a href="single-multi-user.html">Single and Multi-User Flavors</a></li>
<li><a href="infra-support.html">Supported Infrastructures</a></li>
</ul>
</li>
<li>
<a href="#">Che on Docker</a>
<ul>
<li><a href="docker-single-user.html">Docker - Single User</a></li>
<li><a href="docker-multi-user.html">Docker - Multi User</a></li>
<li><a href="docker-config.html">Docker - Configuration</a></li>
<li><a href="docker-cli.html">Docker - CLI Reference</a></li>
</ul>
</li>
<li>
<a href="#">Che on Kubernetes</a>
<ul>
<li><a href="kubernetes-single-user.html">Kubernetes - Single User</a></li>
<li><a href="kubernetes-multi-user.html">Kubernetes - Multi User</a></li>
<li><a href="kubernetes-config.html">Kubernetes - Configuration</a></li>
<li><a href="kubernetes-admin-guide.html">Kubernetes - Admin Guide</a></li>
</ul>
</li>
<li>
<a href="#">Che on OpenShift</a>
<ul>
<li><a href="openshift-single-user.html">OpenShift - Single User</a></li>
<li><a href="openshift-multi-user.html">OpenShift - Multi User</a></li>
<li><a href="openshift-config.html">OpenShift - Configuration</a></li>
<li><a href="openshift-admin-guide.html">OpenShift - Admin Guide</a></li>
</ul>
</li>
<li>
<a href="#">User Management</a>
<ul>
<li><a href="user-management.html">Authentication and Authorization</a></li>
<li><a href="authentication.html">Security Model</a></li>
<li><a href="permissions.html">Permissions</a></li>
<li><a href="organizations.html">Organizations in UD</a></li>
<li><a href="resource-management.html">Resource Management</a></li>
</ul>
</li>
<li>
<a href="#">User Guides</a>
<ul>
<li><a href="creating-starting-workspaces.html">Creating and starting Workspaces</a></li>
<li><a href="ide-projects.html">Projects</a></li>
<li><a href="editor-code-assistance.html">Editor and Code-Assistance</a></li>
<li><a href="dependency-management.html">Dependency Management</a></li>
<li><a href="commands-ide-macro.html">Commands and IDE Macros</a></li>
<li><a href="version-control.html">Version Control</a></li>
<li><a href="debug.html">Debug</a></li>
</ul>
</li>
<li>
<a href="#">Workspace Administration</a>
<ul>
<li><a href="what-are-workspaces.html">Workspace Overview</a></li>
<li><a href="stacks.html">Workspace - Stacks</a></li>
<li><a href="recipes.html">Workspace - Recipes</a></li>
<li><a href="servers.html">Workspace - Servers</a></li>
<li><a href="installers.html">Workspace - Installers</a></li>
<li><a href="volumes.html">Workspace - Volumes Mount</a></li>
<li><a href="env-variables.html">Workspace - Environment Variables</a></li>
<li><a href="projects.html">Workspace - Projects</a></li>
<li><a href="workspaces-troubleshooting.html">Workspace - Troubleshooting</a></li>
<li><a href="workspace-data-model.html">Workspace Data Model</a></li>
</ul>
</li>
<li>
<a href="#">Portable Workspaces</a>
<ul>
<li><a href="chedir-getting-started.html">Chedir - Getting Started</a></li>
<li><a href="why-chedir.html">Chedir - Why Chedir?</a></li>
<li><a href="chedir-installation.html">Chedir - Installation</a></li>
<li><a href="chedir-project-setup.html">Chedir - Project Setup</a></li>
<li><a href="chedir-up-and-down.html">Chedir - Up and Down</a></li>
<li><a href="chefile.html">Chedir - Chefile</a></li>
<li><a href="chedir-ssh.html">Chedir - SSH</a></li>
<li><a href="factories-getting-started.html">Factory - Getting Started</a></li>
<li><a href="creating-factories.html">Factory - Creating</a></li>
<li><a href="factories_json_reference.html">Factory - JSON Reference</a></li>
</ul>
</li>
<li>
<a href="#">Developer Guides</a>
<ul>
<li><a href="framework-overview.html">Overview</a></li>
<li><a href="rest-api.html">SDK - REST API</a></li>
<li><a href="che-in-che-quickstart.html">SDK - Your First Plugin</a></li>
<li><a href="build-reqs.html">SDK - Building Che</a></li>
<li><a href="assemblies.html">SDK - Assemblies</a></li>
<li><a href="logging.html">SDK - Logging</a></li>
<li><a href="ide-extensions-gwt.html">SDK - GWT IDE Extensions</a></li>
<li><a href="server-side-extensions.html">SDK - Server Side Extensions</a></li>
<li><a href="custom-installers.html">SDK - Installers</a></li>
<li><a href="project-types.html">SDK - Project Types</a></li>
<li><a href="language-servers.html">SDK - Language Support</a></li>
<li><a href="parts.html">IDE UI&#58 Parts</a></li>
<li><a href="actions.html">IDE UI&#58 Actions</a></li>
</ul>
</li>
<li>
<a href="#">Dev Essentials</a>
<ul>
<li><a href="guice.html">Dependency Injection</a></li>
<li><a href="dto.html">Transport&#58 DTO</a></li>
<li><a href="json-rpc.html">Communication&#58 JSON-RPC</a></li>
<li><a href="handling-projects-in-plugins.html">Handling Projects in Plugins</a></li>
<li><a href="dao.html">Persistence, DAO</a></li>
<li><a href="properties.html">Properties</a></li>
</ul>
</li>
<li>
<a href="#">Infrastructure and SPI</a>
<ul>
<li><a href="spi_overview.html">Overview</a></li>
<li class="active"><a href="spi-implementation.html">Implementation Notes</a></li>
</ul>
</li>
<!-- if you aren't using the accordion, uncomment this block:
<p class="external">
<a href="#" id="collapseAll">Collapse All</a> | <a href="#" id="expandAll">Expand All</a>
</p>
-->
</ul>
<!-- this highlights the active parent class in the navgoco sidebar. this is critical so that the parent expands when you're viewing a page. This must appear below the sidebar code above. Otherwise, if placed inside customscripts.js, the script runs before the sidebar code runs and the class never gets inserted.-->
<script>$("li.active").parents('li').toggleClass("active");</script>
</div>
<!-- Content Column -->
<div class="col-md-9" id="tg-sb-content">
<div class="post-header">
<h1 class="post-title-main">SPI&#58 Implementation</h1>
</div>
<div class="post-content">
<!-- this handles the automatic toc. use ## for subheads to auto-generate the on-page minitoc. if you use html tags, you must supply an ID for the heading element in order for it to appear in the minitoc. -->
<script>
$( document ).ready(function() {
// Handler for .ready() called.
$('#toc').toc({ minimumHeaders: 0, listType: 'ul', showSpeed: 0, headers: 'h2' });
/* this offset helps account for the space taken up by the floating toolbar. */
$('#toc').on('click', 'a', function() {
var target = $(this.getAttribute('href'))
, scroll_target = target.offset().top
$(window).scrollTop(scroll_target - 10);
return false
})
});
</script>
<div id="toc"></div>
<!--
-->
<div class="paragraph">
<p>Now that you have made yourself familiar with <a href="spi_overview.html">SPI motivation and concept</a>, let’s take a closer look at its components and implementation notes.</p>
</div>
<div class="sect1">
<h2 id="components">Components</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="environment">Environment</h3>
<div class="ulist">
<ul>
<li>
<p><a href="https://github.com/eclipse/che/blob/master/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/environment/InternalEnvironment.java#L32">InternalEnvironment</a> holds internal representations of environment configuration.</p>
</li>
<li>
<p><a href="https://github.com/eclipse/che/blob/master/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/environment/InternalEnvironmentFactory.java#L45">InternalEnvironmentFactory</a> creates a valid <code>InternalEnvironment</code> based on the specified workspace configuration and recipe. This component is independent of Infrastructure</p>
</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="infrastructure">Infrastructure</h3>
<div class="ulist">
<ul>
<li>
<p><a href="https://github.com/eclipse/che/blob/master/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/RuntimeInfrastructure.java">RuntimeInfrastructure</a> is a starting point that has two obligations:</p>
<div class="ulist">
<ul>
<li>
<p>provides meta information about infrastructure like name and types of supported recipes;</p>
</li>
<li>
<p>provides an interface for creation of <code>RuntimeContext</code>.</p>
</li>
</ul>
</div>
</li>
<li>
<p><a href="https://github.com/eclipse/che/blob/master/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/RuntimeContext.java">RuntimeContext</a> holds information that can be used by <code>InternalRuntime</code>.</p>
</li>
<li>
<p><a href="https://github.com/eclipse/che/blob/master/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/InternalRuntime.java">InternalRuntime</a> describes particular runtime and provides an interface for interacting with it like starting, stopping.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Environment components are decoupled from Infrastructure components to make it easier to support the same environments type by different infrastructures.</p>
</div>
<div class="paragraph">
<p>The following diagram shows the components interaction while a workspace start.</p>
</div>
<div class="imageblock">
<div class="content">
<img src="/che/docs/images/spi/start-sequence-diagram.png" alt="start sequence diagram">
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="exceptions">Exceptions</h2>
<div class="sectionbody">
<div class="paragraph">
<p>There are three types of exceptions which can be thrown by Infrastructure components:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>ValidationException</code> should be thrown when an error occurs because of wrong data provided by user (e.g. recipe);</p>
</li>
<li>
<p><code>InternalInfrastructureException</code> should be thrown when an unexpected error occurs and a user has no ways of fixing it, so Che Server administrator should take a look at it;</p>
</li>
<li>
<p><code>InfrastructureException</code> should be thrown in all other cases.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="events">Events</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Infrastructure should inform Workspace API about progress of starting/stopping a workspace with publishing of events. Events are <a href="dto.html">DTOs objects</a> and can be created with <code>DtoFactory</code>. Server implementations are provided by <a href="https://github.com/eclipse/che/tree/master/wsmaster/che-core-api-workspace">workspace-api</a> module. Infrastructure should publish them by <code>EventService</code>. Information about particular events and when they should be thrown are listed below.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="internalenvironmentfactory">InternalEnvironmentFactory</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The first thing that should be decided is which environments will be supported by a new infrastructure, and which recipes this environment supports. Che has four recipes out of the box which can be used: <code>dockerfile</code>, <code>dockerimage</code>, <code>compose</code>, <code>openshift</code>. If a developer wants to support own recipe type the corresponding <code>InternalEnvironmentFactory</code> should be bound with a <code>MapBinder</code> with unique String key that contains recipe type, like:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="java"> MapBinder.newMapBinder(binder(), <span class="predefined-type">String</span>.class, InternalEnvironmentFactory.class);
.addBinding(<span class="string"><span class="delimiter">&quot;</span><span class="content">myRecipe</span><span class="delimiter">&quot;</span></span>).to(SubclassOfInternalEnvironmentFactory.class);</code></pre>
</div>
</div>
<div class="paragraph">
<p>Once supported environments are taken care of, the next step is implementing own <code>RuntimeInfrastructure, RuntimeContext, InternalRuntime</code> which are strongly connected.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="runtimeinfrastructure">RuntimeInfrastructure</h2>
<div class="sectionbody">
<div class="paragraph">
<p>So, <code>RuntimeInfrastructure</code> should create <code>RuntimeContext</code> according to the specified <code>InternalEnvironment</code>. Before context creation, <code>RuntimeInfrastructure</code> may somehow need to customize <code>InternalEnvironment</code>, like modifying original recipes objects according to machines configuration (e.g. exposing ports required for configured servers, or setting memory limit according to machine configuration). This step is called <strong>Runtime Context preparing</strong>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="runtimecontext">RuntimeContext</h2>
<div class="sectionbody">
<div class="paragraph">
<p><strong>Implementation of RuntimeContext</strong> should provide two methods:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>#getOutputChannel</code> method that returns an output channel for a particular runtime. The output channel is a WebSocket endpoint where runtime output can be received by clients. Infrastructure can set up own endpoint or use an existing one.</p>
<div class="paragraph">
<p>Existing master websocket endpoint is configured with <code>che.websocket.endpoint</code> property. Out of the box, there is <code>installer/log</code> request handler that will publish received event via EventService and two <a href="json-rpc.html">JSON RPC</a> messengers which transmit <a href="https://github.com/eclipse/che/blob/master/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/InstallerLogEvent.java">InstallerLogEvent</a> and <a href="https://github.com/eclipse/che/blob/master/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/MachineLogEvent.java">MachineLogEvent</a> via WebSocket to <code>machine/log</code> and <code>installer/log</code> methods. So, received installers logs will be automatically propagated to subscribed clients. If an infrastructure wants to propagate machine logs it’s enough to propagate <code>MachineLogEvent</code> via <code>EventService</code>.</p>
</div>
<div class="paragraph">
<p>Setting up a new endpoint can be useful to remove load from master since there can be a lot of output produced by runtime.</p>
</div>
</li>
<li>
<p><code>#getRuntime</code> method that returns a runtime.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="internalruntime">InternalRuntime</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The main piece of work should be done while implementing of start and stop of <code>InternalRuntime</code> (<code>#internalStart</code> and <code>#internalStop</code> methods). After runtime start, it should be fully ready to be used by clients. Start of runtime includes two major actions: <strong>machines start</strong> and <strong>launching agents</strong>.</p>
</div>
<div class="paragraph">
<p>Starting of machines is an exceptionally infrastructure specific process. There is only one declared thing that is related to machine life cycle: Infrastructure may publish <a href="https://github.com/eclipse/che/blob/master/wsmaster/che-core-api-workspace-shared/src/main/java/org/eclipse/che/api/workspace/shared/dto/event/MachineStatusEvent.java#L25">machine statuses events</a>. There are four possible values for machine status:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong><em>STARTING</em></strong> - machine is starting;</p>
</li>
<li>
<p><strong><em>RUNNING</em></strong> - machine is running, note that it says nothing about servers statuses and machine can be not ready for using by clients;</p>
</li>
<li>
<p><strong><em>STOPPED</em></strong> - machine is not running;</p>
</li>
<li>
<p><strong><em>FAILED</em></strong> - machine failed to start or crashed while running.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="installers">Installers</h2>
<div class="sectionbody">
<div class="paragraph">
<p>As to launching of agents, Che offers an out-of-the-box feature that automates this process. So, it can be done with installers which in fact are shell scripts that should be executed to install some software and dependencies, and launch it if needed. A <strong>bootstrapper</strong> can be used to execute installers scripts.</p>
</div>
<div class="paragraph">
<p><strong>Bootstrapper</strong> is a binary file which can execute installers scripts and wait until the corresponding servers are accessible. To launch machine with agents via a bootstrapper, extend <code>AbstractBootstrapper</code> and implement <code>#doBootstrapAsync</code> method with the following steps:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Provide binary file of bootstrapper into a machine. Bootstrapper binaries are hosted by Che Server and can be downloaded by the following link <code><a href="http://${CHE_HOST}:${CHE_PORT}/agent-binaries/linux_amd64/bootstrapper/bootstrapper" class="bare">http://${CHE_HOST}:${CHE_PORT}/agent-binaries/linux_amd64/bootstrapper/bootstrapper</a></code>. Or it can be provided in any another way if infrastructure is able put something into machine (exec, mount etc).</p>
</li>
<li>
<p>The second step is providing a config file for a bootstrapper (inject it into machine in a way that an infrastructure allows). It must contain an ordered list of <a href="https://github.com/eclipse/che/blob/master/wsmaster/che-core-api-installer-shared/src/main/java/org/eclipse/che/api/installer/shared/model/Installer.java">installers</a> configs in a json format. Installers will be launched in the specified order and it is needed for resolving dependencies between installers. Note that <a href="https://github.com/eclipse/che/blob/master/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/spi/environment/InternalMachineConfig.java#L76">InternalMachineConfig#getInstallers</a> methods returns already ordered installers list.</p>
</li>
<li>
<p>Finally, launching bootstrapper with the corresponding parameters. Bootstrapper requires the following parameters to be specified as start arguments:</p>
<div class="ulist">
<ul>
<li>
<p><strong><em>machine-name</em></strong> - machine name where this particular bootstrapper is running.</p>
</li>
<li>
<p><strong><em>runtime-id</em></strong> - runtime identifier in format 'workspace:environment:owner'.</p>
</li>
<li>
<p><strong><em>push-endpoint</em></strong> - a WebSocket endpoint where to push statuses.</p>
</li>
<li>
<p><strong><em>push-logs-endpoint</em></strong> - a WebSocket endpoint where to push logs.</p>
<div class="paragraph">
<p>The following parameters are optional and there is default behavior when they are missing:</p>
</div>
</li>
<li>
<p><strong><em>-installer-timeout</em></strong> - time(in seconds) given for one installer to complete its installation. If installation is not finished in time it will be interrupted. Default value 120 seconds (3 minutes);</p>
</li>
<li>
<p><strong><em>server-check-period</em></strong> - time(in seconds) between servers availability checks. Once servers for an installer are available, checks are stopped. The default value is 3 seconds;</p>
</li>
<li>
<p><strong><em>file</em></strong> - configuration file path. Default value - <code>config.json</code>;</p>
</li>
<li>
<p><strong><em>logs-endpoint-reconnect-period</em></strong> - time(in seconds) between attempts to reconnect to push-logs-endpoint. Bootstrapper tries to reconnect to push-logs-endpoint when previously established connection lost. Default value - 10 seconds.</p>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="paragraph">
<p>Skeletal implementation of <code>AbstractBootstrapper</code> should look like the following sample:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">org.eclipse.che.workspace.infrastructure.dummy.bootstrapper</span>;
<span class="keyword">import</span> ...;
<span class="directive">public</span> <span class="type">class</span> <span class="class">MyBootstrapper</span> <span class="directive">extends</span> AbstractBootstrapper {
<span class="directive">private</span> <span class="directive">static</span> <span class="directive">final</span> Gson GSON = <span class="keyword">new</span> GsonBuilder().disableHtmlEscaping().create();
<span class="directive">private</span> <span class="directive">final</span> <span class="predefined-type">String</span> machineName;
<span class="directive">private</span> <span class="directive">final</span> RuntimeIdentity runtimeIdentity;
<span class="directive">private</span> <span class="directive">final</span> <span class="predefined-type">List</span>&lt;Installer&gt; installers;
<span class="directive">private</span> <span class="directive">final</span> <span class="type">int</span> serverCheckPeriodSeconds;
<span class="directive">private</span> <span class="directive">final</span> <span class="type">int</span> installerTimeoutSeconds;
<span class="directive">private</span> <span class="directive">final</span> <span class="predefined-type">String</span> installerWebsocketEndpoint;
<span class="directive">private</span> <span class="directive">final</span> <span class="predefined-type">String</span> outputWebsocketEndpoint;
<span class="directive">public</span> MyBootstrapper(
<span class="annotation">@Assisted</span> <span class="predefined-type">String</span> machineName,
<span class="annotation">@Assisted</span> RuntimeIdentity runtimeIdentity,
<span class="annotation">@Assisted</span> <span class="predefined-type">List</span>&lt;Installer&gt; installers,
EventService eventService,
<span class="annotation">@Named</span>(<span class="string"><span class="delimiter">&quot;</span><span class="content">che.websocket.endpoint</span><span class="delimiter">&quot;</span></span>) <span class="predefined-type">String</span> cheWebsocketEndpoint,
<span class="annotation">@Named</span>(<span class="string"><span class="delimiter">&quot;</span><span class="content">che.infra.dummy.output_endpoint</span><span class="delimiter">&quot;</span></span>) <span class="predefined-type">String</span> myOutputEndpoint,
<span class="annotation">@Named</span>(<span class="string"><span class="delimiter">&quot;</span><span class="content">che.infra.dummy.bootstrapper.timeout_min</span><span class="delimiter">&quot;</span></span>) <span class="type">int</span> bootstrappingTimeoutMinutes,
<span class="annotation">@Named</span>(<span class="string"><span class="delimiter">&quot;</span><span class="content">che.infra.dummy.bootstrapper.installer_timeout_sec</span><span class="delimiter">&quot;</span></span>) <span class="type">int</span> installerTimeoutSeconds,
<span class="annotation">@Named</span>(<span class="string"><span class="delimiter">&quot;</span><span class="content">che.infra.dummy.bootstrapper.server_check_period_sec</span><span class="delimiter">&quot;</span></span>) <span class="type">int</span> serverCheckPeriodSeconds) {
<span class="local-variable">super</span>(machineName, runtimeIdentity, bootstrappingTimeoutMinutes, myOutputEndpoint,
cheWebsocketEndpoint, eventService);
<span class="local-variable">this</span>.machineName = machineName;
<span class="local-variable">this</span>.runtimeIdentity = runtimeIdentity;
<span class="local-variable">this</span>.installers = installers;
<span class="local-variable">this</span>.serverCheckPeriodSeconds = serverCheckPeriodSeconds;
<span class="local-variable">this</span>.installerTimeoutSeconds = installerTimeoutSeconds;
<span class="local-variable">this</span>.installerWebsocketEndpoint = cheWebsocketEndpoint;
<span class="local-variable">this</span>.outputWebsocketEndpoint = myOutputEndpoint;
}
<span class="annotation">@Override</span>
<span class="directive">protected</span> <span class="type">void</span> doBootstrapAsync(<span class="predefined-type">String</span> installerWebsocketEndpoint, <span class="predefined-type">String</span> outputWebsocketEndpoint)
<span class="directive">throws</span> InfrastructureException {
<span class="comment">// inject bootstrapper binaries</span>
<span class="comment">// make it executable</span>
<span class="predefined-type">String</span> configJson = GSON.toJson(installers);
<span class="comment">// inject config.json file</span>
<span class="comment">// launch bootstrapper with the corresponding</span>
<span class="comment">// configuration parameters</span>
<span class="comment">//</span>
<span class="comment">// ./bootstrapper -machine-name $machineName</span>
<span class="comment">// -runtime-id + String.format(&quot;%s:%s:%s&quot;,</span>
<span class="comment">// runtimeIdentity.getWorkspaceId(),</span>
<span class="comment">// runtimeIdentity.getEnvName(),</span>
<span class="comment">// runtimeIdentity.getOwner()</span>
<span class="comment">// -push-endpoint $installerWebsocketEndpoint</span>
<span class="comment">// -push-logs-endpoint $outputWebsocketEndpoint</span>
<span class="comment">// -server-check-period $serverCheckPeriodSeconds</span>
<span class="comment">// -installer-timeout $installerTimeoutSeconds</span>
<span class="comment">// -file $pathToConfigFileHere</span>
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>When it is implemented <code>InternalRuntime</code> can easily use it in the following way:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="type">class</span> <span class="class">MyInternalRuntime</span> <span class="directive">extends</span> InternalRuntime&lt;MyRuntimeContext&gt; {
<span class="directive">private</span> <span class="type">void</span> doBootstrap(<span class="predefined-type">String</span> machineName, InternalMachineConfig machineConfig)
<span class="directive">throws</span> InfrastructureException, <span class="exception">InterruptedException</span> {
MyBootstrapper myBootstrapper =
myBootstrapperFactory.create(
machineName, getContext().getIdentity(), machineConfig.getInstallers());
myBootstrapper.bootstrap();
}
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="servers">Servers</h2>
<div class="sectionbody">
<div class="paragraph">
<p>See: <a href="#servers">Servers</a></p>
</div>
<div class="paragraph">
<p>Machine configuration contains servers configuration that will be launched inside it. Servers can be embedded into a machine or launched by installers. If an application has more than one endpoint (like http and websocket, or the same protocol by different paths) it can declare different servers. There are two kinds of servers:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Internal servers which are available only for other machines of a workspace;</p>
</li>
<li>
<p>Public server which are available for all clients.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Public servers should be protected with authentication since they are publicly accessible. Internal servers don’t require any authentication since they must be accessible only for other machines. So, servers configs have the following format:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="comment">/** Configuration of server that can be started inside of machine. */</span>
<span class="directive">public</span> <span class="type">interface</span> <span class="class">ServerConfig</span> {
<span class="comment">/**
* Port used by server. It may contain protocol(tcp or udp) after '/' symbol. If protocol is
* missing tcp will be used by default.
* Example: '8080/tcp', '8080/udp', '8080'.
*/</span>
<span class="predefined-type">String</span> getPort();
<span class="comment">/**
* Protocol for configuring preview url of this server.
* Example: 'http', 'https', 'tcp', 'udp', 'ws', 'wss'.
*/</span>
<span class="predefined-type">String</span> getProtocol();
<span class="comment">/** Path used by server. */</span>
<span class="annotation">@Nullable</span>
<span class="predefined-type">String</span> getPath();
<span class="comment">/** Attributes of the server */</span>
<span class="predefined-type">Map</span>&lt;<span class="predefined-type">String</span>, <span class="predefined-type">String</span>&gt; getAttributes();
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>There is an attribute of which has name <code>internal</code> and boolean value which indicates whether server should be internal or public. If the attribute is missing or its value differs from "true" than server is treated as public. Infrastructure is responsible for propagated ports of servers in different ways depending on whether or not the server is internal. Port is a machine port which will be used by a server and should be propagated by infrastructure for the clients. The way of propagating of machine port is infrastructure specific. To interact with servers Che clients use servers provided by an Infrastructure. So, the server object has the following format:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span class="type">interface</span> <span class="class">Server</span> {
<span class="comment">/** Returns URL exposing the server */</span>
<span class="predefined-type">String</span> getUrl();
<span class="comment">/** Returns the status */</span>
ServerStatus getStatus();
<span class="comment">/** Returns attributes of the server with some metadata */</span>
<span class="predefined-type">Map</span>&lt;<span class="predefined-type">String</span>, <span class="predefined-type">String</span>&gt; getAttributes();
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Attributes from server configs should be propagated to servers.</p>
</div>
<div class="paragraph">
<p>The server URL should be evaluated by Infrastructure with <code>protocol</code> and <code>path</code> from server config, while <code>host name</code> and <code>port</code> values depending on the way in which machines ports are propagated by an infrastructure. Note that server URL can be rewritten with <code>URLRewriter</code> by abstract <code>InternalRuntime</code>, so clients will get modified URL. It is used when Che machines supposed to be accessible via reverse Proxy.</p>
</div>
<div class="paragraph">
<p>As to server statuses they are provided by Infrastructure. There are three possible values for them:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>RUNNING is returned when server is up and running;</p>
</li>
<li>
<p>STOPPED is returned when server is not running;</p>
</li>
<li>
<p>UNKNOWN there is no information about server status.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Checking of servers statuses can be performed in infrastructure specific way. Or Workspace API provides out of box server checkers which can be used by Infrastructure. Now there are servers checks only for most critical servers for Che clients: wsagent, terminal, exec. It can be used by Runtime while starting and waiting until servers are available in the following way:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">import</span> <span class="include">org.eclipse.che.api.workspace.server.hc.ServersChecker</span>;
<span class="keyword">import</span> <span class="include">org.eclipse.che.api.workspace.server.hc.ServersCheckerFactory</span>;
<span class="directive">private</span> <span class="type">class</span> <span class="class">MyInternalRuntime</span> {
...
private <span class="type">void</span> doWaitServersRunning(<span class="predefined-type">String</span> machineName, InternalMachineConfiug machineConfig)
<span class="directive">throws</span> InfrastructureException, <span class="exception">InterruptedException</span> {
ServersChecker readinessChecker = serverCheckerFactory.create(getContext().getIdentity(),
machineName, machine.getServers());
readinessChecker.startAsync((serverRef) -&gt; {
<span class="comment">// update server state to RUNNING</span>
sendRunningServerEvent(machineName, serverRef);
});
readinessChecker.await();
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>In addition there are server probes which may be scheduled to continuously checks server liveness. Here is an example of how to do this:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">import</span> <span class="include">org.eclipse.che.api.workspace.server.hc.probe.ProbeResult</span>;
<span class="keyword">import</span> <span class="include">org.eclipse.che.api.workspace.server.hc.probe.ProbeResult.ProbeStatus</span>;
<span class="keyword">import</span> <span class="include">org.eclipse.che.api.workspace.server.hc.probe.ProbeScheduler</span>;
<span class="keyword">import</span> <span class="include">org.eclipse.che.api.workspace.server.hc.probe.WorkspaceProbesFactory</span>;
<span class="directive">private</span> <span class="type">class</span> <span class="class">MyInternalRuntime</span> {
<span class="directive">private</span> <span class="directive">final</span> ProbeScheduler probeScheduler;
<span class="directive">private</span> <span class="directive">final</span> WorkspaceProbesFactory probesFactory;
...
private <span class="type">void</span> doScheduleServersLivenessProbes(<span class="predefined-type">String</span> machineName) <span class="directive">throws</span> InfrastructureException {
RuntimeIdentity identity = getContext().getIdentity();
probeScheduler.schedule(
probesFactory.getProbes(identity.getWorkspaceId(), machineName, <span class="comment">/*resolved machine servers should be here instead of empty map*/</span> emptyMap()),
<span class="keyword">new</span> ServerLivenessHandler());
}
<span class="directive">private</span> <span class="type">class</span> <span class="class">ServerLivenessHandler</span> <span class="directive">implements</span> Consumer&lt;ProbeResult&gt; {
<span class="annotation">@Override</span>
<span class="directive">public</span> <span class="type">void</span> accept(ProbeResult probeResult) {
<span class="predefined-type">String</span> machineName = probeResult.getMachineName();
<span class="predefined-type">String</span> serverName = probeResult.getServerName();
ProbeStatus probeStatus = probeResult.getStatus();
Server server = <span class="comment">/*assign server which has name serverName to which probe is related instead of null*/</span> <span class="predefined-constant">null</span>;
ServerStatus oldServerStatus = server.getStatus();
ServerStatus serverStatus;
<span class="keyword">if</span> (probeStatus == ProbeStatus.FAILED &amp;&amp; oldServerStatus == ServerStatus.RUNNING) {
serverStatus = ServerStatus.STOPPED;
} <span class="keyword">else</span> <span class="keyword">if</span> (probeStatus == ProbeStatus.PASSED &amp;&amp; (oldServerStatus != ServerStatus.RUNNING)) {
serverStatus = ServerStatus.RUNNING;
} <span class="keyword">else</span> {
<span class="keyword">return</span>;
}
<span class="comment">// set new status serverStatus into machine with name machineName</span>
<span class="comment">// send event about changing server status</span>
}
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="volumes">Volumes</h2>
<div class="sectionbody">
<div class="paragraph">
<p>A <a href="#volumes">volume</a> is a persistent storage that can be used for sharing data between machines of a workspace or for saving data persistently. Volumes field in a machine configuration is a map where key is volume name and value is volume itself. For now, volume object has only one field <code>path</code>. It’s an absolute path where volume should be mounted in the machine.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="CodeRay highlight"><code data-lang="json">{
<span class="key"><span class="delimiter">&quot;</span><span class="content">myMachine</span><span class="delimiter">&quot;</span></span>: {
<span class="error">.</span><span class="error">.</span><span class="error">.</span>
<span class="key"><span class="delimiter">&quot;</span><span class="content">volumes</span><span class="delimiter">&quot;</span></span>: {
<span class="key"><span class="delimiter">&quot;</span><span class="content">maven_repo</span><span class="delimiter">&quot;</span></span>: {
<span class="key"><span class="delimiter">&quot;</span><span class="content">path</span><span class="delimiter">&quot;</span></span>: <span class="string"><span class="delimiter">&quot;</span><span class="content">/home/user/.m2/repository</span><span class="delimiter">&quot;</span></span>
}
}
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Note that if a volume with the same name is used in different machines then the same volume should be shared between machines.</p>
</div>
<div class="paragraph">
<p>Infrastructure must implement supporting of volumes in its own way.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="workspace-start-interruption">Workspace Start Interruption</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Workspace API allows users to stop workspaces that are <strong><em>STARTING</em></strong>, but whether they will interrupt the launch of the workspace or not, depends on the implementation of the infrastructure. So <code>InternalRuntime</code> should expect that <code>internalStop</code> method may be called when <code>internalStart</code> hasn’t finished its work yet. Then <code>InternalRuntime</code> may interrupt runtime start or throw an exception if stopping of a workspace with a <strong><em>STARTING</em></strong> status is not supported by an infrastructure.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="runtimes-recovering">Runtimes Recovering</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Workspace API allows Infrastructure to pick up running runtime while getting it from context. It allows recovering running runtime when workspace master crashed, or restart/reconfigure/update workspace master without workspaces stopping.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="skeletal-implementation">Skeletal Implementation</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The full skeletal implementation is located in the following <a href="https://github.com/codenvy/che-infra-sample">repository</a>.</p>
</div>
</div>
</div>
<div class="tags">
</div>
<!--
-->
</div>
<hr class="shaded"/>
<footer>
<div class="row">
<div class="col-lg-12 footer">
Eclipse Che - Documentation <br/>
Site last generated: Sep 13, 2018 <br/>
<hr>
<a href="http://www.eclipse.org" target="_blank">Eclipse Foundation</a><br/>
<a href="http://www.eclipse.org/legal/privacy.php" target="_blank">Privacy Policy</a><br/>
<a href="http://www.eclipse.org/legal/termsofuse.php" target="_blank">Terms of Use</a><br/>
<a href="https://www.eclipse.org/legal/epl-2.0/" target="_blank">Eclipse Public License</a><br/>
<a href="http://www.eclipse.org/legal" target="_blank">Legal Resources</a><br/>
</div>
</div>
</footer>
<!-- /.row -->
</div>
<!-- /.container -->
</div>
<!-- /#main -->
</div>
</body>
</html>