Oved Ourfali has uploaded a new change for review. Change subject: docker: new docker plugin ......................................................................
docker: new docker plugin This patch adds a docker UI plugin, which allows you to create a VM that runs a docker image. See README file for more details. Change-Id: I6fbeecd38207815399f1e4afc86dd57465a010a9 Signed-off-by: Oved Ourfali <oourf...@redhat.com> --- A docker-plugin/README A docker-plugin/docker-resources/icon_help.png A docker-plugin/docker-resources/launch-docker-dialog.html A docker-plugin/docker-resources/plugin.html A docker-plugin/docker-resources/progress.gif A docker-plugin/docker.json 6 files changed, 468 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/samples-uiplugins refs/changes/14/25814/1 diff --git a/docker-plugin/README b/docker-plugin/README new file mode 100644 index 0000000..e6dfcd7 --- /dev/null +++ b/docker-plugin/README @@ -0,0 +1,28 @@ +Docker plugin +------------------- + +This plugin allows to create an oVirt VM that runs a docker image inside it. +Requirements: +1. The template that has a docker, cloud-init, and oVirt-guest agent installed +2. The docker service should be configured with the "-r" option, to make sure the docker container is restarted after rebooting the VM + +INSTALLING + +1. Configure oVirt Engine HTTP(S) origin(s), and the available docker images, either via editing the existing plugin config file ($PLUGIN_HOME/docker.json), or by creating a new user plugin config file in $ENGINE_ETC/ui-plugins/docker-config.json +- The current config file has some docker images configured, as well as the "localhost" origin + +Expose plugin metadata to oVirt Engine + $ ln -s $PLUGIN_HOME/docker.json $ENGINE_USR/ui-plugins/docker.json + +Expose plugin static files to oVirt Engine + $ ln -s $PLUGIN_HOME/docker-resources $ENGINE_USR/ui-plugins/docker-resources + +USAGE: +1. Fill in the DC, Cluster, Template (template that has the requirements above), VM properties, Docker details (image, port mapping and command if needed), and cloud-init data. +2. Run the created VM + +Notes: +1. If the docker image already exists in the template then the docker container will probably be started quickly. +However, if it isn't then it will be downloaded from the docker public registry, and that may take a while, depnding on the size of the image. +2. The list of containers is currently hard-coded in the plugin configuration file. The code to get the list from the external public docker image repository is available, but it requires JSONP support to prevent cross-origin issues. + diff --git a/docker-plugin/docker-resources/icon_help.png b/docker-plugin/docker-resources/icon_help.png new file mode 100644 index 0000000..fd0f452 --- /dev/null +++ b/docker-plugin/docker-resources/icon_help.png Binary files differ diff --git a/docker-plugin/docker-resources/launch-docker-dialog.html b/docker-plugin/docker-resources/launch-docker-dialog.html new file mode 100644 index 0000000..0b6ba06 --- /dev/null +++ b/docker-plugin/docker-resources/launch-docker-dialog.html @@ -0,0 +1,236 @@ +<!DOCTYPE html> +<html> +<head></head> +<style> + +.select { + border: 1px solid gray; + background-color: white; + width: 265px; +} + +.body { + margin: 0 !important; + color: #333; + font-family: Arial Unicode MS, Arial, sans-serif; + font-size: small; +} + +.form { + top: 0; + position: absolute; +} + +</style> +<body class="body"> +<script src="http://code.jquery.com/jquery-latest.js"></script> +<p> + +<form name="launchform" class="form"> +<table style="width:100%; background-color: #e5e5e5; padding: 5px; margin: 0px;"> +<col width="250px"> +<tr> +<td>Data-Center</td><td><select name="dcs" size="1" id="dcs" class="target select"> +</select></td> +</tr> +<tr> +<td>Cluster</td><td><select name="clusters" size="1" class="select"> +</select></td> +</tr> +<tr> +<td>Template</td><td><select name="templates" size="1" class="select"> +</select></td> +</tr> +</table> +<br/> +<table> +<col width="250px"> +<tr><td>Name</td><td><input type="text" name="name" class="select"></td></tr> +<tr><td>Number of Sockets</td><td><input type="text" name="sockets" class="select"></td></tr> +<tr><td>Number of Cores</td><td><input type="text" name="cores" class="select"></td></tr> +<tr><td>Memory Size (in GB)</td><td><input type="text" name="memory" class="select"></td></tr> +</table> +<br/> +<br/> +Docker Details:<br/> +<table style="width:100%"> +<col width="250px"> +<tr> +<td>Image</td><td><select name="images" size="1" class="select"> +</select></td> +</tr> +<tr><td>Port Mapping (e.g. 80:80)<img src="icon_help.png" title="Map a network port to the container"</td><td><input type="text" name="port" class="select" title="Map a network port to the container"></td></tr> +<tr><td>Command<img src="icon_help.png" title="Needed in images without an entrypoint"></td><td><input type="text" name="command" class="select" title="Needed in images without an entrypoint"></td></tr> +</table> + +<br/> +Cloud-Init Data:<br/> +<table style="width:100%"> +<col width="250px"> +<tr><td>Host name</td><td><input type="text" name="hostname" class="select"></td></tr> +<tr><td>SSH key</td><td><input type="text" name="sshkey" class="select"></td></tr> +</table> +</form> +<div style="top:0; line-height: 430px; width:100%; height:100%; background-color: white; text-align: center; vertical-align: middle; display:none; position: absolute; z-index:9999;" id="progress"> +<img src="progress.gif"> +</div> + + +</p> + + +<script type='text/javascript'> + +function template(templateName) { + this.name = templateName; + this.toJsonString = function () { return JSON.stringify(this); }; +}; + +function cluster(clusterName) { + this.name = clusterName; + this.toJsonString = function () { return JSON.stringify(this); }; +}; + +function vm(vmName, vmTemplateName, vmClusterName, vmMemory, vmCpuTopologySockets, vmCpuTopologyCores, initialization) { + this.name = vmName; + this.template = new template(vmTemplateName); + this.cluster = new cluster(vmClusterName); + this.memory = vmMemory; + this.cpu = new cpu(new topology(vmCpuTopologySockets, vmCpuTopologyCores)); + this.initialization = initialization; + this.toJsonString = function () { return JSON.stringify(this); }; +}; + +function topology(sockets, cores) { + this.sockets = sockets; + this.cores = cores; + this.toJsonString = function () { return JSON.stringify(this); }; +} + +function cpu(topology) { + this.topology = topology; + this.toJsonString = function () { return JSON.stringify(this); }; +} + +function createDockerCustomScript(image, command, port) { + // We call docker service restart and + var prefix = 'runcmd:\n- [ service, docker, restart ]\n- [ sleep, 10 ]\n- [ docker, run, -d'; + var suffix = ' ]'; + var result = prefix; + + // Setting port mapping if it is set + if (port) { + result += ', -p, "' + port + '"'; + } + + // Setting the image + result += ', "' + image + '"'; + + // Setting a command if it is set + if (command.trim()) { + result += ', "' + command.trim().replace(" ", " , ") + '"'; + } + + result += suffix; + + return result; +} + +function initialization(hostName, sshKey, regenerateSshKeys, customScript) { + this.host_name = hostName; + this.authorized_ssh_keys = sshKey; + this.regenerate_ssh_keys = regenerateSshKeys; + this.custom_script = customScript; + this.toJsonString = function () { return JSON.stringify(this); }; +} + +function createNewVm(sessionId, apiEntryPoint, vm) { + var dialog = window; + var vmsUrl = apiEntryPoint + "/vms"; + jQuery.ajax({ + type: "POST", + url: vmsUrl, + headers: { 'engineSessionId' : sessionId, 'Prefer' : 'persistent-auth' }, + data: vm.toJsonString(), + contentType: "application/json; charset=utf-8", + dataType: "json", + + success: function (data, status, jqXHR) { + alert('VM created successfully'); + + // Closing the dialog + parent.postMessage('CloseDialog', '*'); + }, + + error: function (jqXHR, status) { + // Showing the error + alert(JSON.stringify(jqXHR)); + + // Hiding the progress widget + $( "#progress" ).hide(); + } + }); + + // Showing the progress widget + $( "#progress" ).show(); +} + +function dcSelectionChangedEvent() { + var selectedIndex = document.launchform.dcs.options.selectedIndex; + parent.postMessage('GetClustersAndTemplates-' + document.launchform.dcs.options[selectedIndex].value, '*'); +} + +// The plugin should call this function in response to GetDataCenterScore message +function updateDataCenters(dcs) { + for (var index in dcs) { + document.launchform.dcs.options[index] = new Option(dcs[index], dcs[index], false, false); + } + dcSelectionChangedEvent(); +}; + +function updateClusters(clusters) { + for (var index in clusters) { + document.launchform.clusters.options[index] = new Option(clusters[index], clusters[index], false, false); + } +}; + +function updateTemplates(templates) { + for (var index in templates) { + document.launchform.templates.options[index] = new Option(templates[index], templates[index], false, false); + } +}; + +function updateImages(images) { + for (var index in images) { + document.launchform.images.options[index] = new Option(images[index], images[index], false, false); + } +}; + +function addVm(sessionId, apiEntryPoint) { + var name = document.launchform.name.value; + var template = document.launchform.templates.options[document.launchform.templates.options.selectedIndex].value; + var cluster = document.launchform.clusters.options[document.launchform.clusters.options.selectedIndex].value; + var memory = document.launchform.memory.value*1024*1024*1024; + var sockets = document.launchform.sockets.value; + var cores = document.launchform.cores.value; + var hostName = document.launchform.hostname.value; + var sshKey = document.launchform.sshkey.value; + var command = document.launchform.command.value; + var image = document.launchform.images.options[document.launchform.images.options.selectedIndex].value; + var port = document.launchform.port.value; + var customScript = createDockerCustomScript(image, command, port); + + createNewVm(sessionId, apiEntryPoint, new vm(name, template, cluster, memory, sockets, cores, new initialization(hostName, sshKey, false, customScript))); +} + + parent.postMessage('GetDataCenters', '*'); + parent.postMessage('GetImages', '*'); + + $( "#dcs" ).change(function() { + dcSelectionChangedEvent(); + }); + +</script> + +</body> +</html> diff --git a/docker-plugin/docker-resources/plugin.html b/docker-plugin/docker-resources/plugin.html new file mode 100644 index 0000000..916efca --- /dev/null +++ b/docker-plugin/docker-resources/plugin.html @@ -0,0 +1,183 @@ +<!doctype html> +<!-- + oVirt Docker Plugin +--> +<html> +<head> + <meta charset="utf-8"> +</head> +<body> + +<script src="http://code.jquery.com/jquery-latest.js"></script> +<script type='text/javascript'> + +var formWindow = null; +var restSessionId = ''; +var api = parent.pluginApi('docker'); + +// Get runtime plugin configuration, i.e. custom configuration (if any) +// merged on top of default configuration (if any) +var config = api.configObject(); + + +function getDCList(sessionId, apiEntryPoint) { + var dcs = new Array(); + var dcsUrl = apiEntryPoint + "/datacenters"; + jQuery.ajax({ + type: "GET", + dataType: "json", + url: dcsUrl, + headers: { 'engineSessionId' : sessionId, 'Prefer' : 'persistent-auth' }, + success: function(data) { + for (var index in data.data_center) { + dcs[index] = data.data_center[index].name; + } + formWindow.updateDataCenters(dcs); + } + }); +} + +function setImageList(images) { + formWindow.updateImages(images); +} + +// Currently not in use. +// Useful for getting the images from the public repo, once it supports JSONP +function getImageList(query) { + var images = new Array(); + jQuery.ajax({ + type: "GET", + dataType: "json", + url: "https://index.docker.io/v1/search?q=" + encodeURIComponent(query), + success: function(data) { + for (var index in data.results) { + images[index] = data.results[index].name + " (" + data.results[index].description + ")"; + } + formWindow.updateImages(images); + }, + error: function (jqXHR, status) { + alert(JSON.stringify(jqXHR)); + } + }); +} + +function getClusterList(sessionId, apiEntryPoint, dcName) { + var clusters = new Array(); + var clustersUrl = apiEntryPoint + "/clusters?search=" + encodeURIComponent('datacenter=' + dcName); + jQuery.ajax({ + type: "GET", + dataType: "json", + url: clustersUrl, + headers: { 'engineSessionId' : sessionId, 'Prefer' : 'persistent-auth' }, + success: function(data) { + for (var index in data.cluster) { + clusters[index] = data.cluster[index].name; + } + formWindow.updateClusters(clusters); + } + }); +} + +function getTemplateList(sessionId, apiEntryPoint, dcName) { + var templates = new Array(); + var templatesUrl = apiEntryPoint + "/templates?search=" + encodeURIComponent('datacenter=' + dcName); + jQuery.ajax({ + type: "GET", + dataType: "json", + url: templatesUrl, + headers: { 'engineSessionId' : sessionId, 'Prefer' : 'persistent-auth' }, + success: function(data) { + for (var index in data.template) { + templates[index] = data.template[index].name; + } + formWindow.updateTemplates(templates); + } + }); +} + +function addVm() { + formWindow.addVm(restSessionId, config.apiEntryPoint); +} + + // Customize API options that affect specific features of plugin API + api.options({ + // Configure source origin(s), i.e. protocol://domain:port + // from which HTML5 message events will be accepted + allowedMessageOrigins: config.allowedOrigins + }); + + var init = function() { + api.addMainTabActionButton('VirtualMachine', 'Create Docker VM', + { + isEnabled: function() { + return arguments.length == 0; + }, + onClick: function() { + api.showDialog('Create Docker VM', 'launch-docker', + '/ovirt-engine/webadmin/plugin/docker/launch-docker-dialog.html', + '540px', '500px', + { + buttons: [ + { + label: 'Cancel', + onClick: function() { + api.closeDialog('launch-docker'); + } + }, + { + label: 'OK', + onClick: addVm + } + ], + resizeEnabled: true, + closeIconVisible: true, + closeOnEscKey: true + }); + } + } + ); + }; + + // Register event handler functions for later invocation by UI plugin infrastructure + api.register({ + + // Called by the infrastructure as part of plugin initialization, + // will be called just once during the lifetime of a plugin + UiInit: function() { + init(); + }, + RestApiSessionAcquired: function(sessionId) { + restSessionId = sessionId; + alert(restSessionId); + }, + MessageReceived: function(data, sourceWindow) { + // If we get here, we already passed allowed source origin check + var eventDataPair = data.split('-'); + switch (eventDataPair[0]) { + case 'GetDataCenters': + formWindow = sourceWindow; + getDCList(restSessionId, config.apiEntryPoint); + break; + case 'GetClustersAndTemplates': + formWindow = sourceWindow; + getClusterList(restSessionId, config.apiEntryPoint, eventDataPair[1]); + getTemplateList(restSessionId, config.apiEntryPoint, eventDataPair[1]); + break; + case 'GetImages': + formWindow = sourceWindow; + setImageList(config.dockerImages); + break; + case 'CloseDialog': + api.closeDialog('launch-docker'); + break; + } + }, + }); + + // Tell plugin infrastructure that we are good to go, expect UiInit callback + api.ready(); + +</script> + +</body> +</html> diff --git a/docker-plugin/docker-resources/progress.gif b/docker-plugin/docker-resources/progress.gif new file mode 100644 index 0000000..d5fc984 --- /dev/null +++ b/docker-plugin/docker-resources/progress.gif Binary files differ diff --git a/docker-plugin/docker.json b/docker-plugin/docker.json new file mode 100644 index 0000000..f97d663 --- /dev/null +++ b/docker-plugin/docker.json @@ -0,0 +1,21 @@ +{ + + // Unique name of the plugin + "name": "docker", + + // URL of plugin host page that bootstraps plugin code + // This URL maps to $ENGINE_USR/ui-plugins/docker-resources/plugin.html + "url": "plugin/docker/plugin.html", + + // Path to plugin resource files relative to descriptor + // This path maps to $ENGINE_USR/ui-plugins/space-shooter-resources + "resourcePath": "docker-resources", + + // Default configuration associated with the plugin + "config": { + "allowedOrigins": ["http://localhost:8700"], + "apiEntryPoint": "http://localhost:8700/ovirt-engine/api", + "dockerImages": ["fedora", "ubuntu", "cirros", "goldmann/wildfly-cluster:front-end"] + } + +} -- To view, visit http://gerrit.ovirt.org/25814 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I6fbeecd38207815399f1e4afc86dd57465a010a9 Gerrit-PatchSet: 1 Gerrit-Project: samples-uiplugins Gerrit-Branch: master Gerrit-Owner: Oved Ourfali <oourf...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches