[Kubernetes] Deploy Kubernetes using murano.

Change-Id: Ic09f5ca401fe60d06d606d327335d0ebe1e13628
This commit is contained in:
Alexey Khivin 2016-10-03 14:35:14 +03:00
parent d7be94d354
commit 2ed6c6c4be
6 changed files with 1026 additions and 0 deletions

71
kubernetes/pom.xml Normal file
View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>2.11</version>
<relativePath/>
</parent>
<groupId>com.mirantis.plugins</groupId>
<artifactId>murano</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>hpi</packaging>
<properties>
<jenkins.version>1.625.3</jenkins.version>
<java.level>7</java.level>
<jenkins-test-harness.version>2.13</jenkins-test-harness.version>
</properties>
<name>Murano Kubernetes Plugin</name>
<description>Murano plugin with K8S to deploy Tomcat WAR</description>
<url>https://wiki.jenkins-ci.org/display/JENKINS/TODO+Plugin</url>
<licenses>
<license>
<name>MIT License</name>
<url>http://opensource.org/licenses/MIT</url>
</license>
</licenses>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>org.pacesys</groupId>
<artifactId>openstack4j-core</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.pacesys.openstack4j.connectors</groupId>
<artifactId>openstack4j-httpclient</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>1.4.4</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,30 @@
package com.mirantis.plugins.murano;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Configuration Helper to understand the Mirantis UI definition
*/
public class ConfigurationSection {
Map<String, List<Map>> conf;
public ConfigurationSection() {
conf = new HashMap();
}
public void addSection(String sectionName, List fields) {
conf.put(sectionName, fields);
}
public List<Map> getSection(String sectionName){
return conf.get(sectionName);
}
public Set getSections(){
return conf.keySet();
}
}

View File

@ -0,0 +1,765 @@
package com.mirantis.plugins.murano;
import com.mirantis.plugins.murano.client.OpenstackClient;
import hudson.Extension;
import hudson.Launcher;
import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import io.fabric8.kubernetes.api.model.ReplicationController;
import io.fabric8.kubernetes.api.model.ReplicationControllerBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.openstack4j.connectors.httpclient.HttpCommand;
import org.openstack4j.core.transport.HttpMethod;
import org.openstack4j.core.transport.HttpRequest;
import org.yaml.snakeyaml.Yaml;
import javax.servlet.ServletException;
import java.io.*;
import java.security.SecureRandom;
import java.util.*;
/**
* Main Jenkins plugin class that acts as a PostBuilder
*/
public class MuranoBuilder extends Notifier {
private String serverUrl;
private String username;
private String password;
private String tenantName;
private String dockerImage;
private String flavor;
private String keypairs;
private String clusterName;
private String slaveCount;
private String gatewayCount;
@DataBoundConstructor
public MuranoBuilder(String serverUrl,
String username,
String password,
String tenantName,
String clusterName,
String dockerRegistry,
String flavor,
String keypairs,
String dockerImage,
String slaveCount,
String gatewayCount) {
this.serverUrl = serverUrl;
this.username = username;
this.password = password;
this.tenantName = tenantName;
this.dockerImage = dockerImage;
this.clusterName = clusterName;
this.flavor = flavor;
this.keypairs = keypairs;
this.slaveCount = slaveCount;
this.gatewayCount = gatewayCount;
}
public String getServerUrl() {
return serverUrl;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getTenantName() {
return tenantName;
}
public String getDockerImage() {
return dockerImage;
}
public String getFlavor() {
return flavor;
}
public String getClusterName() {
return clusterName;
}
public String getGatewayCount() {
return gatewayCount;
}
public String getSlaveCount() {
return slaveCount;
}
/**
* Postbuild implementation, gets the parameters from the values filled in the Jenkins
* Configuration.
*
* It the uses the Environment name to check if the environment exists and if it does, uses the
* same environment to deploy the image. This also assumes that a build solution is available that
* pushes the Jenkins build to dockerhub or whatever Docker registry has been configured. Thats out
* of scope of this plugin to build the Docker image.
*
* If there is no environment available, it will build the K8S environment and then use it to
* deploy the image.
*
* @param build The Build object from Jenkins
* @param launcher The Launcher
* @param listener Listener for Logging events
* @return whether the post-build was successful
* @throws IOException
* @throws InterruptedException
*/
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener) throws IOException, InterruptedException {
// Replace the image so that $BUILD_NUMBER is replaced with an actual image. This is because
// we tag the docker images with the build numbers
String image = Util.replaceMacro(this.dockerImage, build.getEnvironment(listener));
OpenstackClient client = new OpenstackClient(serverUrl,
username,
password,
tenantName);
if (!client.authenticate()) {
listener.getLogger().println("Cannot connect to Openstack server. Pls check configuration");
return false;
}
// Get the UI Definition for Kubernetes. This is hardcoded based on the K8S Definition in Murano
// TODO: See if this can be changed dynamically
String token = client.getOSClient().getAccess().getToken().getId();
HttpRequest request = HttpRequest.builder().method(HttpMethod.GET)
.endpoint(serverUrl + ":9292")
.path("/v3/artifacts/murano/v1/f07d4447-d479-401d-86f2-902f3e260f60/ui_definition/download") // K8S cluster id
.header("X-Auth-Token", token)
.build();
HttpCommand command = HttpCommand.create(request);
CloseableHttpResponse response = null;
try {
response = command.execute();
} catch(Exception ex) {
ex.printStackTrace();;
return false;
}
if (response == null) {
listener.getLogger().println("Error getting response for UI definition");
return false;
}
StringBuffer jsonString = new StringBuffer();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(
response.getEntity().getContent()));
//Print the raw output of murano api from the server
String output;
while ((output = br.readLine()) != null) {
jsonString.append(output + "\n");
}
} catch(Exception ex) {
listener.getLogger().println("Error getting output for UI definition");
return false;
}
// Fill in the templates according to the jenkins job
// TODO: Could be done only if the image needs to be created. Otehrwise its a waste of resouces.
Yaml yaml = new Yaml();
String appTemplate = "";
ConfigurationSection sections = new ConfigurationSection();
Map templates = new HashMap();
Map<String, Map<String, Object>> values = (Map<String, Map<String, Object>>) yaml.load(jsonString.toString());
for (String key : values.keySet()) {
System.out.println(key);
if (key.equals("Forms")) {
List<Map> formElements = (ArrayList<Map>)values.get(key);
for (Map type: formElements) {
String typeName = (String)type.keySet().toArray()[0]; // gets the form types like : appConfiguration, instanceConfiguration etc
Map<String, Map<String, String>> fields = (HashMap<String, Map<String,String>>) type.get(typeName);
List<Map> fieldArr = (ArrayList<Map>)fields.get("fields");
sections.addSection(typeName, fieldArr);
}
} else if (key.equals("Application")) {
System.out.println("Application section : ");
Map<String, Object> applicationTemplate = (Map<String, Object>)values.get(key);
appTemplate = "{" + getApplicationJsonTemplate(applicationTemplate) + "}";
} else if (key.equals("Templates")) {
System.out.println("Templates");
Map<String, Object> templateMap = values.get(key);
for(String templateName: templateMap.keySet()) {
Object templateValue = templateMap.get(templateName);
if (templateValue instanceof Map) {
Map<String, Object> internalTemplateHash = (Map<String, Object>) templateValue;
//String templateExpanded = "{" + getApplicationJsonTemplate(internalTemplateHash) + "}";
templates.put(templateName, internalTemplateHash);
}
}
}
}
String envId = checkIfDeploymentExists(token, this.clusterName);
if (envId == null) {
listener.getLogger().println("Creating new enviroment");
// No Environment, create the cluster
String filledTemplate = fillTemplatesForKubernetes(templates,
appTemplate,
build.getId(),
this.flavor,
this.keypairs);
// Create Env
envId = this.createEnvironment(token, this.clusterName);
// Create Session
String sessionId = this.createEnvironmentSession(token, envId);
// Add App to Environment
addApplicationToEnvironment(token, envId, sessionId, filledTemplate);
// Deploy
deployEnvironment(token, envId, sessionId);
listener.getLogger().println("Waiting for Deployment to finish...");
if (!isDeploymentSuccess(token, envId)) {
listener.getLogger().println("Taking longer to deploy, please check the murano console");
return false;
}
listener.getLogger().println("Deployed K8S");
}
// Get the Floating IP to deploy the image
String kubeMasterAddress = null;
try {
kubeMasterAddress = getFloatingIp(token, envId);
kubeMasterAddress = "http://" + kubeMasterAddress + ":8080"; // By default the mirantis instance runs on 8080
} catch(Exception ex) {
listener.getLogger().println("Error getting Floating ip");
return false;
}
return deployImage(kubeMasterAddress, this.clusterName, image, listener.getLogger());
}
/**
* Loop around to see if the deployment is a success. This waits for about 10 secs 300 times hoping that
* it finishes. This all depends on teh number of nodes and the speed of the boxes. But seems sufficient.
*
* @param token Environment Token
* @param envId Environemnt Id
* @return whether the deployment is a success
*/
private boolean isDeploymentSuccess(String token, String envId) {
boolean status = false;
for (int i=0; i<300; i++) {
try {
Thread.sleep(10000);
String payload = getResponseForJsonPost(serverUrl,
8082,
"/v1/environments/" + envId + "/deployments",
HttpMethod.GET,
token,
null,
null);
JSONParser parser = new JSONParser();
try {
JSONObject deployments = (JSONObject) parser.parse(payload);
JSONArray deploymentList = (JSONArray) deployments.get("deployments");
JSONObject thisDeployment = (JSONObject) deploymentList.get(0);
if ("success".equals((String) thisDeployment.get("state"))) {
status = true;
break;
}
} catch (ParseException pe) {
System.out.println("position: " + pe.getPosition());
System.out.println(pe);
}
} catch (Exception ex) {
status = false;
break;
}
}
return status;
}
// ServiceName is the same as Cluster Name
private boolean deployImage(String floatingIp, String serviceName, String image, PrintStream logger) {
Config config = new ConfigBuilder()
.withMasterUrl(floatingIp)
.withNamespace("default") // Use default namespace
.build();
// Redo the dance till the cleartext is setup with kubernetes libraries.
// TODO: This is fixed in https://github.com/fabric8io/kubernetes-client/issues/498
DefaultKubernetesClient client = new DefaultKubernetesClient(config);
ArrayList<ConnectionSpec> specs = new ArrayList<ConnectionSpec>();
specs.add(ConnectionSpec.CLEARTEXT);
OkHttpClient httpClient = client.getHttpClient().newBuilder().connectionSpecs(specs).build();
client = new DefaultKubernetesClient(httpClient, config);
Service service = client.services().withName(serviceName).get();
if (service == null) {
// There is no service, create one with port on 9999 targeting 8080 internally
logger.println("There is no service named: " + serviceName + ", Creating one");
HashMap<String,String> labels = new HashMap<String,String>();
labels.put("server", "javawebapp");
client.services().createNew().
withNewMetadata().withName(serviceName).endMetadata().
withNewSpec().
addNewPort().withPort(9999).withNewTargetPort().withIntVal(8080).endTargetPort().endPort().
withSelector(labels).
withType("NodePort").
endSpec().
done();
service = client.services().withName(serviceName).get();
if (service == null) {
logger.print("Still cannot create servce, Aborting");
return false;
}
} else {
logger.println("Obtained Service with name: " + serviceName);
}
// Create the RC
logger.print("Creating Replication Controller now");
ReplicationController rc = client.replicationControllers().withName(serviceName + "-rc").get();
if (rc == null) {
logger.println("No RC Found with name: " + serviceName + "-rc, Creating one");
rc = new ReplicationControllerBuilder().
withNewMetadata().withName(serviceName + "-rc").addToLabels("server", "javawebapp").endMetadata().
withNewSpec().withReplicas(1).
withNewTemplate().
withNewMetadata().addToLabels("server", "javawebapp").endMetadata().
withNewSpec().
addNewContainer().withName(serviceName).withImage(image).
addNewPort().withContainerPort(8080).endPort().
endContainer().
endSpec().
endTemplate().
endSpec().
build();
client.replicationControllers().create(rc);
logger.println("Created image with: " + image);
} else {
logger.println("RC Found with name: " + serviceName + "-rc, updating with new image");
client.replicationControllers().
withName(serviceName + "-rc").
rolling().updateImage(image);
logger.println("Updated image to : " + image);
}
return true;
}
/* Standard output is of form
[{"gatewayCount": 1, "gatewayNodes": [{"instance": {"availabilityZone": "nova", "openstackId": "184c7bca-b757-429e-8565-96a4b32927c8", "name": "$.instanceConfiguration.unitNamingPattern-e094401173c7716f88cc36d62cbb31fe.com", "securityGroupName": null, "image": "ubuntu14.04-x64-kubernetes", "assignFloatingIp": true, "floatingIpAddress": "172.17.10.116", "keyname": "Raja_macbookpro", "?": {"classVersion": "0.0.0", "name": null, "package": "io.murano", "type": "io.murano.resources.LinuxMuranoInstance", "_actions": {}, "id": "ed3fc4457be3056cf500fcd216dbf25c"}, "ipAddresses": ["10.0.31.5", "172.17.10.116"], "flavor": "m1.medium", "networks": {"useFlatNetwork": false, "primaryNetwork": null, "useEnvironmentNetwork": true, "customNetworks": []}, "sharedIps": []}, "?": {"classVersion": "0.0.0", "name": null, "package": "io.murano.apps.docker.kubernetes.KubernetesCluster", "type": "io.murano.apps.docker.kubernetes.KubernetesGatewayNode", "_actions": {}, "id": "b8b3ebc3dd3cce4deed702c48c9475bf"}}], "?": {"classVersion": "0.0.0", "status": "ready", "name": null, "package": "io.murano.apps.docker.kubernetes.KubernetesCluster", "type": "io.murano.apps.docker.kubernetes.KubernetesCluster", "_actions": {"e7c2d5b5cf8291fdb0149188f2b8b9ee_scaleNodesUp": {"enabled": true, "name": "scaleNodesUp"}, "e7c2d5b5cf8291fdb0149188f2b8b9ee_scaleGatewaysDown": {"enabled": true, "name": "scaleGatewaysDown"}, "e7c2d5b5cf8291fdb0149188f2b8b9ee_scaleGatewaysUp": {"enabled": true, "name": "scaleGatewaysUp"}, "e7c2d5b5cf8291fdb0149188f2b8b9ee_exportConfig": {"enabled": true, "name": "exportConfig"}, "e7c2d5b5cf8291fdb0149188f2b8b9ee_scaleNodesDown": {"enabled": true, "name": "scaleNodesDown"}}, "id": "e7c2d5b5cf8291fdb0149188f2b8b9ee"}, "serviceEndpoints": [], "nodeCount": 1, "dockerRegistry": null, "masterNode": {"instance": {"availabilityZone": "nova", "openstackId": "ee92608e-8eb7-41ac-a53a-e8c5a8c107b4", "name": "$.instanceConfiguration.unitNamingPattern-92a53dbcd69636a793587c564e2f708a.com", "securityGroupName": null, "image": "ubuntu14.04-x64-kubernetes", "assignFloatingIp": true, "floatingIpAddress": "172.17.10.115", "keyname": "Raja_macbookpro", "?": {"classVersion": "0.0.0", "name": null, "package": "io.murano", "type": "io.murano.resources.LinuxMuranoInstance", "_actions": {}, "id": "dabbc3a6f0fa18d781ed88369b738a90"}, "ipAddresses": ["10.0.31.4", "172.17.10.115"], "flavor": "m1.medium", "networks": {"useFlatNetwork": false, "primaryNetwork": null, "useEnvironmentNetwork": true, "customNetworks": []}, "sharedIps": []}, "?": {"classVersion": "0.0.0", "name": null, "package": "io.murano.apps.docker.kubernetes.KubernetesCluster", "type": "io.murano.apps.docker.kubernetes.KubernetesMasterNode", "_actions": {}, "id": "882094bb08fa2f78c0607e613d79f428"}}, "minionNodes": [{"instance": {"availabilityZone": "nova", "openstackId": "db281114-019c-4b57-9fb1-11ae7e3b4fbc", "name": "$.instanceConfiguration.unitNamingPattern-d1b3affe5146ad9dcaa4a55d8d69725b.com", "securityGroupName": null, "image": "ubuntu14.04-x64-kubernetes", "assignFloatingIp": true, "floatingIpAddress": "172.17.10.117", "keyname": "Raja_macbookpro", "?": {"classVersion": "0.0.0", "name": null, "package": "io.murano", "type": "io.murano.resources.LinuxMuranoInstance", "_actions": {}, "id": "f890a089cbf645f8f3599fffb6553fb6"}, "ipAddresses": ["10.0.31.6", "172.17.10.117"], "flavor": "m1.medium", "networks": {"useFlatNetwork": false, "primaryNetwork": null, "useEnvironmentNetwork": true, "customNetworks": []}, "sharedIps": []}, "?": {"classVersion": "0.0.0", "name": null, "package": "io.murano.apps.docker.kubernetes.KubernetesCluster", "type": "io.murano.apps.docker.kubernetes.KubernetesMinionNode", "_actions": {}, "id": "d967c9cf3c53fd1deaae4c4af2de6379"}, "exposeCAdvisor": true}], "name": "K8ST1"}]
*/
private String getFloatingIp(String token, String envId) throws Exception {
String payload = getResponseForJsonPost(serverUrl,
8082,
"/v1/environments/" + envId + "/services",
HttpMethod.GET,
token,
null,
null);
String floatingIp = null;
if (payload != null) {
JSONParser parser = new JSONParser();
JSONArray array = (JSONArray) parser.parse(payload);
floatingIp =
(String) ((JSONArray) ((JSONObject) ((JSONObject) ((JSONObject) array.get(0)).get("masterNode")).
get("instance")).get("ipAddresses")).get(0);
}
return floatingIp;
}
/**
* Return the Environment id if it exists
*
* @param token
* @param name
* @return
*/
private String checkIfDeploymentExists(String token, String name) {
String payload = getResponseForJsonPost(serverUrl,
8082,
"/v1/environments",
HttpMethod.GET,
token,
null,
null);
String envId = null;
JSONParser parser = new JSONParser();
try{
Object obj = parser.parse(payload);
JSONObject response = (JSONObject)obj;
JSONArray environmentArray = (JSONArray) response.get("environments");
for (Object env: environmentArray) {
JSONObject thisEnv = (JSONObject) env;
String envName = (String) thisEnv.get("name");
if (envName.equals(name)) {
envId = (String) thisEnv.get("id");
break;
}
}
}catch(ParseException pe){
System.out.println("position: " + pe.getPosition());
System.out.println(pe);
}
return envId;
}
/**
* Deploy the environment given the environment id and Session Token
*/
private void deployEnvironment(String token, String envId, String sessionId) {
String response = getResponseForJsonPost(serverUrl,
8082,
"/v1/environments/" + envId + "/sessions/" + sessionId + "/deploy",
HttpMethod.POST,
token,
null,
null);
}
/**
* Add the app(K8S) to the environment
* @param token
* @param envId
* @param sessionId
* @param jsonReq
*/
private void addApplicationToEnvironment(String token, String envId, String sessionId, String jsonReq) {
String response = getResponseForJsonPost(serverUrl,
8082,
"/v1/environments/" + envId + "/services",
HttpMethod.POST,
token,
jsonReq,
sessionId);
System.out.println("add application response : " + response);
//return sessionId;
}
private String createEnvironmentSession(String token, String envId) {
String payload = getResponseForJsonPost(serverUrl,
8082,
"/v1/environments/" + envId + "/configure",
HttpMethod.POST,
token,
null,
null);
String sessionId = "";
JSONParser parser = new JSONParser();
try{
Object obj = parser.parse(payload);
JSONObject response = (JSONObject)obj;
sessionId = (String)response.get("id");
}catch(ParseException pe){
System.out.println("position: " + pe.getPosition());
System.out.println(pe);
}
System.out.println("Session Id : " + sessionId);
return sessionId;
}
private String createEnvironment(String token, String envname) {
String reqPayload = "{\"name\":\"" + envname + "\"}";
String payload = getResponseForJsonPost(serverUrl, 8082, "/v1/environments", HttpMethod.POST, token, reqPayload, null);
String envId = "";
JSONParser parser = new JSONParser();
try{
Object obj = parser.parse(payload);
JSONObject response = (JSONObject)obj;
envId = (String)response.get("id");
}catch(ParseException pe){
System.out.println("position: " + pe.getPosition());
System.out.println(pe);
}
System.out.println("Envid : " + envId);
return envId;
}
/**
* Main helper method to call the Murano API and return the response. Accepts both GET and POST.
*
* @param url Base URL to connect
* @param port Which port is murano listening on
* @param requestPath Path on Murano URL
* @param method GET or POST
* @param token Auth Token
* @param jsonPayload Payload for the message
* @param muranoSessionId Optional Session Id
* @return Response from the Call
*/
private String getResponseForJsonPost(String url,
int port,
String requestPath,
HttpMethod method,
String token,
String jsonPayload,
String muranoSessionId) {
HttpRequest request = HttpRequest.builder().method(method)
.endpoint(url + ":" + port)
.path(requestPath) // K8S cluster id
.header("X-Auth-Token", token)
.json(jsonPayload)
.build();
if (muranoSessionId != null) {
request.getHeaders().put("X-Configuration-Session", muranoSessionId);
}
if (jsonPayload != null) {
request = request.toBuilder().json(jsonPayload).build();
}
HttpCommand command = HttpCommand.create(request);
CloseableHttpResponse response = null;
try {
response = command.execute();
} catch(Exception ex) {
ex.printStackTrace();;
return null;
}
StringBuffer jsonString = new StringBuffer();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(
response.getEntity().getContent()));
//Print the raw output of murano api from the server
String output;
while ((output = br.readLine()) != null) {
jsonString.append(output + "\n");
}
} catch(Exception ex) {
return null;
}
return jsonString.toString();
}
private String fillTemplatesForKubernetes(Map templates,
String template,
String buildId,
String flavor,
String keypair) {
String appTemplate = template.replace("$.appConfiguration.name", "Kubernetes_" + buildId);
appTemplate = appTemplate.replace("$.appConfiguration.minionCount", this.slaveCount);
appTemplate = appTemplate.replace("$.appConfiguration.gatewayCount", this.gatewayCount);
appTemplate = appTemplate.replace("dockerRegistry", "");
// Setup KubeMaster
Map<String,Object> masterTemplate = (Map<String,Object>) templates.get("masterNode");
String kubeMasterTemplate = "{" + getApplicationJsonTemplate(masterTemplate) + "}";
kubeMasterTemplate = kubeMasterTemplate.replaceAll("$.instanceConfiguration.unitNamingPattern-.*?.com", "K8sMaster" + buildId); //Name
kubeMasterTemplate = kubeMasterTemplate.replace("$.instanceConfiguration.flavor", flavor);
kubeMasterTemplate = kubeMasterTemplate.replace("$.appConfiguration.assignFloatingIP", "True");
kubeMasterTemplate = kubeMasterTemplate.replace("$.instanceConfiguration.keyPair", keypair);
kubeMasterTemplate = kubeMasterTemplate.replace("$.instanceConfiguration.availabilityZone", "nova");
appTemplate = appTemplate.replace("\"masterNode\":\"$masterNode\"", "\"masterNode\":" + kubeMasterTemplate);
// Kube Minion
StringBuffer sbMinionTemplate = new StringBuffer();
String delim = "";
for(int i = 1; i<= Integer.parseInt(this.slaveCount); i++) {
Map<String,Object> minionTemplate = (Map<String,Object>) templates.get("minionNode");
String kubeMinionTemplate = "{" + getApplicationJsonTemplate(minionTemplate) + "}";
kubeMinionTemplate = kubeMinionTemplate.replaceAll("\\$.instanceConfiguration.unitNamingPattern-.*?.com", "K8sMinion-" + generateUUID()); //Name
kubeMinionTemplate = kubeMinionTemplate.replace("$.instanceConfiguration.flavor", flavor);
kubeMinionTemplate = kubeMinionTemplate.replace("$.appConfiguration.assignFloatingIP", "True");
kubeMinionTemplate = kubeMinionTemplate.replace("$.instanceConfiguration.keyPair", keypair);
kubeMinionTemplate = kubeMinionTemplate.replace("$.instanceConfiguration.availabilityZone", "nova");
sbMinionTemplate.append(delim).append(kubeMinionTemplate);
delim = ",";
}
appTemplate = appTemplate.replace("\"minionNodes\":\"repeat($minionNode, $.appConfiguration.maxMinionCount)\"", "\"minionNodes\": [" + sbMinionTemplate.toString() + "]");
// Kube Gateway
StringBuffer sbGatewayTemplate = new StringBuffer();
delim = "";
for(int i = 1; i<= Integer.parseInt(this.gatewayCount); i++) {
Map<String,Object> gatewayTemplate = (Map<String,Object>) templates.get("gatewayNode");
String gatewayMinionTemplate = "{" + getApplicationJsonTemplate(gatewayTemplate ) + "}";
gatewayMinionTemplate = gatewayMinionTemplate.replaceAll("\\$.instanceConfiguration.unitNamingPattern-.*?.com", "K8sGateway-" + generateUUID()); //Name
gatewayMinionTemplate = gatewayMinionTemplate.replace("$.instanceConfiguration.flavor", flavor);
gatewayMinionTemplate = gatewayMinionTemplate.replace("$.appConfiguration.assignFloatingIP", "True");
gatewayMinionTemplate = gatewayMinionTemplate.replace("$.instanceConfiguration.keyPair", keypair);
gatewayMinionTemplate = gatewayMinionTemplate.replace("$.instanceConfiguration.availabilityZone", "nova");
sbGatewayTemplate.append(delim).append(gatewayMinionTemplate);
delim = ",";
}
appTemplate = appTemplate.replace("\"gatewayNodes\":\"repeat($gatewayNode, $.appConfiguration.maxGatewayCount)\"", "\"gatewayNodes\": [" + sbGatewayTemplate.toString() + "]");
return appTemplate;
}
private String generateUUID() {
SecureRandom ng = new SecureRandom();
long MSB = 0x8000000000000000L;
String str = Long.toHexString(MSB | ng.nextLong()) + Long.toHexString(MSB | ng.nextLong());
return str;
}
private String getApplicationJsonTemplate(Map<String,Object> template) {
String templateStr = "";
boolean firstStr = true;
for (String k : template.keySet()) {
if (!firstStr) {
templateStr += ",";
}
firstStr = false;
Object v = template.get(k);
if (v instanceof Map) {
templateStr += "\"" + k + "\": {" + this.getApplicationJsonTemplate((Map<String, Object>)v) ;
templateStr += "}";
} else if (v instanceof String) {
String uuid = generateUUID();
if (k.equals("type")) {
templateStr += "\"" + k + "\":\"" + v + "\"";
templateStr += ",\"id\":\"" + uuid + "\"";
}else if (k.equals("name") && (((String)v).contains("generateHostname"))) {
templateStr += "\"name\":\"$.instanceConfiguration.unitNamingPattern-" + uuid + ".com\"";
}else {
templateStr += "\"" + k + "\":\"" + v + "\"";
}
}
}
return templateStr;
}
@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.BUILD;
}
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {
public DescriptorImpl() {
load();
}
@Override
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}
public FormValidation doCheckServerUrl(@QueryParameter String serverUrl) {
if (serverUrl.length() == 0)
return FormValidation.error("Please enter a server url");
if (serverUrl.indexOf("://") == -1)
return FormValidation.error("Enter a url of the format http(s)://<server>");
return FormValidation.ok();
}
public FormValidation doTestConnection(@QueryParameter("serverUrl") final String serverUrl,
@QueryParameter("username") final String username,
@QueryParameter("password") final String password,
@QueryParameter("tenantName") final String tenantName)
throws IOException, ServletException {
try {
OpenstackClient client = new OpenstackClient(serverUrl,
username,
password,
tenantName);
if (client.authenticate()) {
return FormValidation.ok("Success");
} else {
return FormValidation.error("Unable to connect to server. Please check credentials");
}
} catch (Exception e) {
return FormValidation.error("Error: "+e.getMessage());
}
}
public ListBoxModel doFillFlavorItems(@QueryParameter("serverUrl") final String serverUrl,
@QueryParameter("username") final String username,
@QueryParameter("password") final String password,
@QueryParameter("tenantName") final String tenantName) {
ListBoxModel flavor = new ListBoxModel();
OpenstackClient client = new OpenstackClient(serverUrl,
username,
password,
tenantName);
client.authenticate(); // Authenticate to Openstack
if (client.getOSClient() != null) {
for (String fl: client.getFlavors()) {
flavor.add(fl);
}
}
return flavor;
}
public ListBoxModel doFillKeypairsItems(@QueryParameter("serverUrl") final String serverUrl,
@QueryParameter("username") final String username,
@QueryParameter("password") final String password,
@QueryParameter("tenantName") final String tenantName) {
ListBoxModel keypairs = new ListBoxModel();
OpenstackClient client = new OpenstackClient(serverUrl,
username,
password,
tenantName);
client.authenticate(); // Authenticate to Openstack
if (client.getOSClient() != null) {
for (String fl: client.getKeypairs()) {
keypairs.add(fl);
}
}
return keypairs;
}
/**
* This human readable name is used in the configuration screen.
*/
public String getDisplayName() {
return "Murano Kubernetes Plugin";
}
}
}

View File

@ -0,0 +1,94 @@
package com.mirantis.plugins.murano.client;
import org.openstack4j.api.OSClient;
import org.openstack4j.model.compute.Flavor;
import org.openstack4j.model.compute.Keypair;
import org.openstack4j.openstack.OSFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Client Class to talk to Openstack/Murano APIs
*/
public class OpenstackClient {
private final static Logger LOG = Logger.getLogger(OpenstackClient.class.getName());
private String serverUrl;
private String username;
private String password;
private String tenantName;
private OSClient.OSClientV2 os = null;
public OpenstackClient(String serverUrl,
String username,
String password,
String tenantName) {
this.serverUrl = serverUrl;
this.username = username;
this.password = password;
this.tenantName = tenantName;
}
/**
* Authenticate to the Openstack instance given the credentials in constructor
* @return whether the auth was successful
*/
public boolean authenticate() {
boolean success = false;
try {
this.os = OSFactory.builderV2()
.endpoint(this.serverUrl + ":5000/v2.0")
.credentials(this.username, this.password)
.tenantName(this.tenantName)
.authenticate();
success = true;
} catch(Exception ex) {
LOG.log(Level.SEVERE, "Error connecting to Client", ex);
success = false;
}
return success;
}
/**
* Get all the Flavors the user has access to
* @return A List of Flavor objects
*/
public ArrayList<String> getFlavors() {
ArrayList<String> flavors = new ArrayList<String>();
if (this.os != null) {
List<? extends Flavor> flavorsList = this.os.compute().flavors().list();
for (Flavor f : flavorsList) {
flavors.add(f.getName());
}
}
return flavors;
}
/**
* Get all the Keypairs the user has access to
* @return A List of Keypair object
*/
public ArrayList<String> getKeypairs() {
ArrayList<String> keypairs = new ArrayList<String>();
if (this.os != null) {
List<? extends Keypair> kpList = this.os.compute().keypairs().list();
for (Keypair k : kpList) {
keypairs.add(k.getName());
}
}
return keypairs;
}
/**
* Helper object to return the OSClient
* @return OSClient V2
*/
public OSClient.OSClientV2 getOSClient() {
return this.os;
}
}

View File

@ -0,0 +1,59 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core"
xmlns:st="jelly:stapler"
xmlns:d="jelly:define"
xmlns:l="/lib/layout"
xmlns:t="/lib/hudson"
xmlns:f="/lib/form">
<f:section title="Server Credentials">
<f:entry title="Server URL" field="serverUrl">
<f:textbox />
</f:entry>
<f:entry title="Username" field="username">
<f:textbox />
</f:entry>
<f:entry title="Password" field="password">
<f:password />
</f:entry>
<f:entry title="Tenant Name" field="tenantName">
<f:textbox />
</f:entry>
<f:validateButton
title="Test Connection" progress="Connecting..."
method="testConnection" with="serverUrl,username,password,tenantName" />
</f:section>
<f:section title="Node Details">
<f:entry title="Kubernetes Cluster Name" field="clusterName">
<f:textbox />
</f:entry>
<f:entry title="Docker Registry" field="dockerRegistry">
<f:textbox />
</f:entry>
<f:entry title="Flavor List" field="flavor">
<f:select />
</f:entry>
<f:entry title="Keypair" field="keypairs">
<f:select />
</f:entry>
</f:section>
<f:section title="Additional Node Details">
<f:advanced>
<f:entry title="Number of Slave Nodes" field="slaveCount">
<f:textbox default="1"/>
</f:entry>
<f:entry title="Number of Gateway Nodes" field="gatewayCount">
<f:textbox default="1"/>
</f:entry>
</f:advanced>
</f:section>
<f:section title="Image Details">
<f:entry title="Docker Image" field="dockerImage">
<f:textbox />
</f:entry>
</f:section>
</j:jelly>

View File

@ -0,0 +1,7 @@
<?jelly escape-by-default='true'?>
<!--
This view is used to render the installed plugins page.
-->
<div>
This plugin is a sample to explain how to write a Jenkins plugin.
</div>