Browse Source

[Kubernetes] Deploy Kubernetes using murano.

Change-Id: Ic09f5ca401fe60d06d606d327335d0ebe1e13628
Alexey Khivin 2 years ago
parent
commit
2ed6c6c4be

+ 71
- 0
kubernetes/pom.xml View File

@@ -0,0 +1,71 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4
+  <modelVersion>4.0.0</modelVersion>
5
+
6
+  <parent>
7
+    <groupId>org.jenkins-ci.plugins</groupId>
8
+    <artifactId>plugin</artifactId>
9
+    <version>2.11</version>
10
+    <relativePath/>
11
+  </parent>
12
+
13
+  <groupId>com.mirantis.plugins</groupId>
14
+  <artifactId>murano</artifactId>
15
+  <version>1.0-SNAPSHOT</version>
16
+  <packaging>hpi</packaging>
17
+
18
+  <properties>
19
+    <jenkins.version>1.625.3</jenkins.version>
20
+    <java.level>7</java.level>
21
+    <jenkins-test-harness.version>2.13</jenkins-test-harness.version>
22
+  </properties>
23
+
24
+  <name>Murano Kubernetes Plugin</name>
25
+  <description>Murano plugin with K8S to deploy Tomcat WAR</description>
26
+  <url>https://wiki.jenkins-ci.org/display/JENKINS/TODO+Plugin</url>
27
+
28
+  <licenses>
29
+    <license>
30
+      <name>MIT License</name>
31
+      <url>http://opensource.org/licenses/MIT</url>
32
+    </license>
33
+  </licenses>
34
+
35
+  <repositories>
36
+    <repository>
37
+      <id>repo.jenkins-ci.org</id>
38
+      <url>https://repo.jenkins-ci.org/public/</url>
39
+    </repository>
40
+  </repositories>
41
+  <pluginRepositories>
42
+    <pluginRepository>
43
+      <id>repo.jenkins-ci.org</id>
44
+      <url>https://repo.jenkins-ci.org/public/</url>
45
+    </pluginRepository>
46
+  </pluginRepositories>
47
+
48
+  <dependencies>
49
+    <dependency>
50
+      <groupId>org.pacesys</groupId>
51
+      <artifactId>openstack4j-core</artifactId>
52
+      <version>3.0.1</version>
53
+    </dependency>
54
+    <dependency>
55
+      <groupId>org.pacesys.openstack4j.connectors</groupId>
56
+      <artifactId>openstack4j-httpclient</artifactId>
57
+      <version>3.0.1</version>
58
+    </dependency>
59
+    <dependency>
60
+      <groupId>com.googlecode.json-simple</groupId>
61
+      <artifactId>json-simple</artifactId>
62
+      <version>1.1.1</version>
63
+    </dependency>
64
+    <dependency>
65
+      <groupId>io.fabric8</groupId>
66
+      <artifactId>kubernetes-client</artifactId>
67
+      <version>1.4.4</version>
68
+    </dependency>
69
+  </dependencies>
70
+
71
+</project>

+ 30
- 0
kubernetes/src/main/java/com/mirantis/plugins/murano/ConfigurationSection.java View File

@@ -0,0 +1,30 @@
1
+package com.mirantis.plugins.murano;
2
+
3
+import java.util.HashMap;
4
+import java.util.List;
5
+import java.util.Map;
6
+import java.util.Set;
7
+
8
+/**
9
+ * Configuration Helper to understand the Mirantis UI definition
10
+ */
11
+public class ConfigurationSection {
12
+    Map<String, List<Map>> conf;
13
+
14
+    public ConfigurationSection() {
15
+        conf = new HashMap();
16
+    }
17
+
18
+    public void addSection(String sectionName, List fields) {
19
+        conf.put(sectionName, fields);
20
+    }
21
+
22
+    public List<Map> getSection(String sectionName){
23
+        return conf.get(sectionName);
24
+    }
25
+
26
+    public Set getSections(){
27
+        return conf.keySet();
28
+    }
29
+}
30
+

+ 765
- 0
kubernetes/src/main/java/com/mirantis/plugins/murano/MuranoBuilder.java View File

@@ -0,0 +1,765 @@
1
+package com.mirantis.plugins.murano;
2
+
3
+
4
+import com.mirantis.plugins.murano.client.OpenstackClient;
5
+import hudson.Extension;
6
+import hudson.Launcher;
7
+import hudson.Util;
8
+import hudson.model.AbstractBuild;
9
+import hudson.model.AbstractProject;
10
+import hudson.model.BuildListener;
11
+import hudson.tasks.BuildStepDescriptor;
12
+import hudson.tasks.BuildStepMonitor;
13
+import hudson.tasks.Notifier;
14
+import hudson.tasks.Publisher;
15
+import hudson.util.FormValidation;
16
+import hudson.util.ListBoxModel;
17
+import io.fabric8.kubernetes.api.model.ReplicationController;
18
+import io.fabric8.kubernetes.api.model.ReplicationControllerBuilder;
19
+import io.fabric8.kubernetes.api.model.Service;
20
+import io.fabric8.kubernetes.client.Config;
21
+import io.fabric8.kubernetes.client.ConfigBuilder;
22
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
23
+import okhttp3.ConnectionSpec;
24
+import okhttp3.OkHttpClient;
25
+import org.apache.http.client.methods.CloseableHttpResponse;
26
+import org.json.simple.JSONArray;
27
+import org.json.simple.JSONObject;
28
+import org.json.simple.parser.JSONParser;
29
+import org.json.simple.parser.ParseException;
30
+import org.kohsuke.stapler.DataBoundConstructor;
31
+import org.kohsuke.stapler.QueryParameter;
32
+import org.openstack4j.connectors.httpclient.HttpCommand;
33
+import org.openstack4j.core.transport.HttpMethod;
34
+import org.openstack4j.core.transport.HttpRequest;
35
+import org.yaml.snakeyaml.Yaml;
36
+
37
+import javax.servlet.ServletException;
38
+import java.io.*;
39
+import java.security.SecureRandom;
40
+import java.util.*;
41
+
42
+/**
43
+ * Main Jenkins plugin class that acts as a PostBuilder
44
+ */
45
+public class MuranoBuilder extends Notifier {
46
+
47
+    private String serverUrl;
48
+    private String username;
49
+    private String password;
50
+    private String tenantName;
51
+    private String dockerImage;
52
+    private String flavor;
53
+    private String keypairs;
54
+    private String clusterName;
55
+    private String slaveCount;
56
+    private String gatewayCount;
57
+
58
+    @DataBoundConstructor
59
+    public MuranoBuilder(String serverUrl,
60
+                         String username,
61
+                         String password,
62
+                         String tenantName,
63
+                         String clusterName,
64
+                         String dockerRegistry,
65
+                         String flavor,
66
+                         String keypairs,
67
+                         String dockerImage,
68
+                         String slaveCount,
69
+                         String gatewayCount) {
70
+        this.serverUrl = serverUrl;
71
+        this.username = username;
72
+        this.password = password;
73
+        this.tenantName = tenantName;
74
+        this.dockerImage = dockerImage;
75
+        this.clusterName = clusterName;
76
+        this.flavor = flavor;
77
+        this.keypairs = keypairs;
78
+        this.slaveCount = slaveCount;
79
+        this.gatewayCount = gatewayCount;
80
+    }
81
+
82
+    public String getServerUrl() {
83
+        return serverUrl;
84
+    }
85
+
86
+    public String getUsername() {
87
+        return username;
88
+    }
89
+
90
+    public String getPassword() {
91
+        return password;
92
+    }
93
+
94
+    public String getTenantName() {
95
+        return tenantName;
96
+    }
97
+
98
+    public String getDockerImage() {
99
+        return dockerImage;
100
+    }
101
+
102
+    public String getFlavor() {
103
+        return flavor;
104
+    }
105
+
106
+    public String getClusterName() {
107
+        return clusterName;
108
+    }
109
+
110
+    public String getGatewayCount() {
111
+        return gatewayCount;
112
+    }
113
+
114
+    public String getSlaveCount() {
115
+        return slaveCount;
116
+    }
117
+
118
+    /**
119
+     * Postbuild implementation, gets the parameters from the values filled in the Jenkins
120
+     * Configuration.
121
+     *
122
+     * It the uses the Environment name to check if the environment exists and if it does, uses the
123
+     * same environment to deploy the image. This also assumes that a build solution is available that
124
+     * pushes the Jenkins build to dockerhub or whatever Docker registry has been configured. Thats out
125
+     * of scope of this plugin to build the Docker image.
126
+     *
127
+     * If there is no environment available, it will build the K8S environment and then use it to
128
+     * deploy the image.
129
+     *
130
+     * @param build The Build object from Jenkins
131
+     * @param launcher The Launcher
132
+     * @param listener Listener for Logging events
133
+     * @return whether the post-build was successful
134
+     * @throws IOException
135
+     * @throws InterruptedException
136
+     */
137
+    @Override
138
+    public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
139
+                           BuildListener listener) throws IOException, InterruptedException {
140
+
141
+        // Replace the image so that $BUILD_NUMBER is replaced with an actual image. This is because
142
+        // we tag the docker images with the build numbers
143
+        String image = Util.replaceMacro(this.dockerImage, build.getEnvironment(listener));
144
+
145
+        OpenstackClient client = new OpenstackClient(serverUrl,
146
+                username,
147
+                password,
148
+                tenantName);
149
+        if (!client.authenticate()) {
150
+            listener.getLogger().println("Cannot connect to Openstack server. Pls check configuration");
151
+            return false;
152
+        }
153
+
154
+        // Get the UI Definition for Kubernetes. This is hardcoded based on the K8S Definition in Murano
155
+        // TODO: See if this can be changed dynamically
156
+        String token = client.getOSClient().getAccess().getToken().getId();
157
+        HttpRequest request = HttpRequest.builder().method(HttpMethod.GET)
158
+                .endpoint(serverUrl + ":9292")
159
+                .path("/v3/artifacts/murano/v1/f07d4447-d479-401d-86f2-902f3e260f60/ui_definition/download") // K8S cluster id
160
+                .header("X-Auth-Token", token)
161
+                .build();
162
+        HttpCommand command = HttpCommand.create(request);
163
+        CloseableHttpResponse response = null;
164
+        try {
165
+            response = command.execute();
166
+        } catch(Exception ex) {
167
+            ex.printStackTrace();;
168
+            return false;
169
+        }
170
+        if (response == null) {
171
+            listener.getLogger().println("Error getting response for UI definition");
172
+            return false;
173
+        }
174
+
175
+        StringBuffer jsonString = new StringBuffer();
176
+        try {
177
+            BufferedReader br = new BufferedReader(new InputStreamReader(
178
+                    response.getEntity().getContent()));
179
+
180
+            //Print the raw output of murano api from the server
181
+            String output;
182
+
183
+            while ((output = br.readLine()) != null) {
184
+                jsonString.append(output + "\n");
185
+            }
186
+        } catch(Exception ex) {
187
+            listener.getLogger().println("Error getting output for UI definition");
188
+            return false;
189
+        }
190
+
191
+        // Fill in the templates according to the jenkins job
192
+        // TODO: Could be done only if the image needs to be created. Otehrwise its a waste of resouces.
193
+        Yaml yaml = new Yaml();
194
+        String appTemplate = "";
195
+        ConfigurationSection sections = new ConfigurationSection();
196
+        Map templates = new HashMap();
197
+        Map<String, Map<String, Object>> values = (Map<String, Map<String, Object>>) yaml.load(jsonString.toString());
198
+        for (String key : values.keySet()) {
199
+            System.out.println(key);
200
+            if (key.equals("Forms")) {
201
+                List<Map> formElements = (ArrayList<Map>)values.get(key);
202
+
203
+                for (Map type: formElements) {
204
+                    String typeName = (String)type.keySet().toArray()[0]; // gets the form types like : appConfiguration, instanceConfiguration etc
205
+
206
+                    Map<String, Map<String, String>> fields = (HashMap<String, Map<String,String>>) type.get(typeName);
207
+                    List<Map> fieldArr = (ArrayList<Map>)fields.get("fields");
208
+
209
+                    sections.addSection(typeName, fieldArr);
210
+
211
+                }
212
+            } else if (key.equals("Application")) {
213
+                System.out.println("Application section : ");
214
+                Map<String, Object> applicationTemplate = (Map<String, Object>)values.get(key);
215
+                appTemplate = "{" + getApplicationJsonTemplate(applicationTemplate) + "}";
216
+
217
+            } else if (key.equals("Templates")) {
218
+                System.out.println("Templates");
219
+                Map<String, Object> templateMap = values.get(key);
220
+                for(String templateName: templateMap.keySet()) {
221
+                    Object templateValue = templateMap.get(templateName);
222
+                    if (templateValue instanceof Map) {
223
+                        Map<String, Object> internalTemplateHash = (Map<String, Object>) templateValue;
224
+                        //String templateExpanded = "{" + getApplicationJsonTemplate(internalTemplateHash) + "}";
225
+                        templates.put(templateName, internalTemplateHash);
226
+                    }
227
+                }
228
+            }
229
+        }
230
+
231
+        String envId = checkIfDeploymentExists(token, this.clusterName);
232
+        if (envId == null) {
233
+            listener.getLogger().println("Creating new enviroment");
234
+            // No Environment, create the cluster
235
+            String filledTemplate = fillTemplatesForKubernetes(templates,
236
+                    appTemplate,
237
+                    build.getId(),
238
+                    this.flavor,
239
+                    this.keypairs);
240
+
241
+            // Create Env
242
+            envId = this.createEnvironment(token, this.clusterName);
243
+
244
+            // Create Session
245
+            String sessionId = this.createEnvironmentSession(token, envId);
246
+
247
+            // Add App to Environment
248
+            addApplicationToEnvironment(token, envId, sessionId, filledTemplate);
249
+
250
+            // Deploy
251
+            deployEnvironment(token, envId, sessionId);
252
+            listener.getLogger().println("Waiting for Deployment to finish...");
253
+            if (!isDeploymentSuccess(token, envId)) {
254
+                listener.getLogger().println("Taking longer to deploy, please check the murano console");
255
+                return false;
256
+            }
257
+            listener.getLogger().println("Deployed K8S");
258
+        }
259
+
260
+
261
+        //  Get the Floating IP to deploy the image
262
+        String kubeMasterAddress = null;
263
+        try {
264
+            kubeMasterAddress = getFloatingIp(token, envId);
265
+            kubeMasterAddress = "http://" + kubeMasterAddress + ":8080";       // By default the mirantis instance runs on 8080
266
+        } catch(Exception ex) {
267
+            listener.getLogger().println("Error getting Floating ip");
268
+            return false;
269
+        }
270
+
271
+        return deployImage(kubeMasterAddress, this.clusterName, image, listener.getLogger());
272
+    }
273
+
274
+    /**
275
+     * Loop around to see if the deployment is a success. This waits for about 10 secs 300 times hoping that
276
+     * it finishes. This all depends on teh number of nodes and the speed of the boxes. But seems sufficient.
277
+     *
278
+     * @param token Environment Token
279
+     * @param envId Environemnt Id
280
+     * @return whether the deployment is a success
281
+     */
282
+    private boolean isDeploymentSuccess(String token, String envId) {
283
+        boolean status = false;
284
+        for (int i=0; i<300; i++) {
285
+
286
+            try {
287
+                Thread.sleep(10000);
288
+                String payload = getResponseForJsonPost(serverUrl,
289
+                        8082,
290
+                        "/v1/environments/" + envId + "/deployments",
291
+                        HttpMethod.GET,
292
+                        token,
293
+                        null,
294
+                        null);
295
+                JSONParser parser = new JSONParser();
296
+                try {
297
+                    JSONObject deployments = (JSONObject) parser.parse(payload);
298
+                    JSONArray deploymentList = (JSONArray) deployments.get("deployments");
299
+                    JSONObject thisDeployment = (JSONObject) deploymentList.get(0);
300
+                    if ("success".equals((String) thisDeployment.get("state"))) {
301
+                        status = true;
302
+                        break;
303
+                    }
304
+                } catch (ParseException pe) {
305
+                    System.out.println("position: " + pe.getPosition());
306
+                    System.out.println(pe);
307
+                }
308
+            } catch (Exception ex) {
309
+                status = false;
310
+                break;
311
+            }
312
+        }
313
+        return status;
314
+    }
315
+
316
+    // ServiceName is the same as Cluster Name
317
+    private boolean deployImage(String floatingIp, String serviceName, String image, PrintStream logger) {
318
+        Config config = new ConfigBuilder()
319
+                .withMasterUrl(floatingIp)
320
+                .withNamespace("default")       // Use default namespace
321
+                .build();
322
+
323
+        // Redo the dance till the cleartext is setup with kubernetes libraries.
324
+        // TODO: This is fixed in https://github.com/fabric8io/kubernetes-client/issues/498
325
+        DefaultKubernetesClient client = new DefaultKubernetesClient(config);
326
+        ArrayList<ConnectionSpec> specs = new ArrayList<ConnectionSpec>();
327
+        specs.add(ConnectionSpec.CLEARTEXT);
328
+        OkHttpClient httpClient = client.getHttpClient().newBuilder().connectionSpecs(specs).build();
329
+        client = new DefaultKubernetesClient(httpClient, config);
330
+
331
+        Service service = client.services().withName(serviceName).get();
332
+        if (service == null) {
333
+            // There is no service, create one with port on 9999 targeting 8080 internally
334
+            logger.println("There is no service named: " + serviceName + ", Creating one");
335
+            HashMap<String,String> labels = new HashMap<String,String>();
336
+            labels.put("server", "javawebapp");
337
+            client.services().createNew().
338
+                    withNewMetadata().withName(serviceName).endMetadata().
339
+                    withNewSpec().
340
+                    addNewPort().withPort(9999).withNewTargetPort().withIntVal(8080).endTargetPort().endPort().
341
+                    withSelector(labels).
342
+                    withType("NodePort").
343
+                    endSpec().
344
+                    done();
345
+            service = client.services().withName(serviceName).get();
346
+            if (service == null) {
347
+                logger.print("Still cannot create servce, Aborting");
348
+                return false;
349
+            }
350
+        } else {
351
+            logger.println("Obtained Service with name: " + serviceName);
352
+        }
353
+
354
+        // Create the RC
355
+        logger.print("Creating Replication Controller now");
356
+
357
+        ReplicationController rc = client.replicationControllers().withName(serviceName + "-rc").get();
358
+        if (rc == null) {
359
+            logger.println("No RC Found with name: " + serviceName + "-rc, Creating one");
360
+            rc = new ReplicationControllerBuilder().
361
+                    withNewMetadata().withName(serviceName + "-rc").addToLabels("server", "javawebapp").endMetadata().
362
+                    withNewSpec().withReplicas(1).
363
+                    withNewTemplate().
364
+                    withNewMetadata().addToLabels("server", "javawebapp").endMetadata().
365
+                    withNewSpec().
366
+                    addNewContainer().withName(serviceName).withImage(image).
367
+                    addNewPort().withContainerPort(8080).endPort().
368
+                    endContainer().
369
+                    endSpec().
370
+                    endTemplate().
371
+                    endSpec().
372
+                    build();
373
+            client.replicationControllers().create(rc);
374
+            logger.println("Created image with: " + image);
375
+        } else {
376
+            logger.println("RC Found with name: " + serviceName + "-rc, updating with new image");
377
+            client.replicationControllers().
378
+                    withName(serviceName + "-rc").
379
+                    rolling().updateImage(image);
380
+            logger.println("Updated image to : " + image);
381
+        }
382
+        return true;
383
+    }
384
+
385
+
386
+    /* Standard output is of form
387
+       [{"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"}]
388
+     */
389
+    private String getFloatingIp(String token, String envId) throws Exception {
390
+        String payload = getResponseForJsonPost(serverUrl,
391
+                8082,
392
+                "/v1/environments/" + envId + "/services",
393
+                HttpMethod.GET,
394
+                token,
395
+                null,
396
+                null);
397
+        String floatingIp = null;
398
+        if (payload != null) {
399
+            JSONParser parser = new JSONParser();
400
+            JSONArray array = (JSONArray) parser.parse(payload);
401
+            floatingIp =
402
+                    (String) ((JSONArray) ((JSONObject) ((JSONObject) ((JSONObject) array.get(0)).get("masterNode")).
403
+                            get("instance")).get("ipAddresses")).get(0);
404
+        }
405
+
406
+        return floatingIp;
407
+    }
408
+    /**
409
+     * Return the Environment id if it exists
410
+     *
411
+     * @param token
412
+     * @param name
413
+     * @return
414
+     */
415
+    private String checkIfDeploymentExists(String token, String name) {
416
+        String payload = getResponseForJsonPost(serverUrl,
417
+                8082,
418
+                "/v1/environments",
419
+                HttpMethod.GET,
420
+                token,
421
+                null,
422
+                null);
423
+        String envId = null;
424
+        JSONParser parser = new JSONParser();
425
+        try{
426
+            Object obj = parser.parse(payload);
427
+            JSONObject response = (JSONObject)obj;
428
+            JSONArray environmentArray =  (JSONArray) response.get("environments");
429
+            for (Object env: environmentArray) {
430
+                JSONObject thisEnv = (JSONObject) env;
431
+                String envName = (String) thisEnv.get("name");
432
+                if (envName.equals(name)) {
433
+                    envId = (String) thisEnv.get("id");
434
+                    break;
435
+                }
436
+            }
437
+        }catch(ParseException pe){
438
+            System.out.println("position: " + pe.getPosition());
439
+            System.out.println(pe);
440
+        }
441
+        return envId;
442
+    }
443
+
444
+    /**
445
+     * Deploy the environment given the environment id and Session Token
446
+     */
447
+    private void deployEnvironment(String token, String envId, String sessionId) {
448
+        String response = getResponseForJsonPost(serverUrl,
449
+                8082,
450
+                "/v1/environments/" + envId + "/sessions/" + sessionId + "/deploy",
451
+                HttpMethod.POST,
452
+                token,
453
+                null,
454
+                null);
455
+    }
456
+
457
+    /**
458
+     * Add the app(K8S) to the environment
459
+     * @param token
460
+     * @param envId
461
+     * @param sessionId
462
+     * @param jsonReq
463
+     */
464
+    private void addApplicationToEnvironment(String token, String envId, String sessionId, String jsonReq) {
465
+        String response = getResponseForJsonPost(serverUrl,
466
+                8082,
467
+                "/v1/environments/" + envId + "/services",
468
+                HttpMethod.POST,
469
+                token,
470
+                jsonReq,
471
+                sessionId);
472
+        System.out.println("add application response  : " + response);
473
+        //return sessionId;
474
+    }
475
+
476
+    private String createEnvironmentSession(String token, String envId) {
477
+        String payload = getResponseForJsonPost(serverUrl,
478
+                8082,
479
+                "/v1/environments/" + envId + "/configure",
480
+                HttpMethod.POST,
481
+                token,
482
+                null,
483
+                null);
484
+
485
+        String sessionId = "";
486
+        JSONParser parser = new JSONParser();
487
+        try{
488
+            Object obj = parser.parse(payload);
489
+            JSONObject response = (JSONObject)obj;
490
+            sessionId = (String)response.get("id");
491
+        }catch(ParseException pe){
492
+            System.out.println("position: " + pe.getPosition());
493
+            System.out.println(pe);
494
+        }
495
+        System.out.println("Session Id : " + sessionId);
496
+        return sessionId;
497
+    }
498
+
499
+    private String createEnvironment(String token, String envname) {
500
+        String reqPayload = "{\"name\":\"" + envname + "\"}";
501
+        String payload = getResponseForJsonPost(serverUrl, 8082, "/v1/environments", HttpMethod.POST, token, reqPayload, null);
502
+
503
+        String envId = "";
504
+        JSONParser parser = new JSONParser();
505
+        try{
506
+            Object obj = parser.parse(payload);
507
+            JSONObject response = (JSONObject)obj;
508
+            envId = (String)response.get("id");
509
+        }catch(ParseException pe){
510
+            System.out.println("position: " + pe.getPosition());
511
+            System.out.println(pe);
512
+        }
513
+        System.out.println("Envid : " + envId);
514
+        return envId;
515
+    }
516
+
517
+    /**
518
+     * Main helper method to call the Murano API and return the response. Accepts both GET and POST.
519
+     *
520
+     * @param url Base URL to connect
521
+     * @param port Which port is murano listening on
522
+     * @param requestPath Path on Murano URL
523
+     * @param method GET or POST
524
+     * @param token Auth Token
525
+     * @param jsonPayload Payload for the message
526
+     * @param muranoSessionId Optional Session Id
527
+     * @return Response from the Call
528
+     */
529
+    private  String getResponseForJsonPost(String url,
530
+                                           int port,
531
+                                           String requestPath,
532
+                                           HttpMethod method,
533
+                                           String token,
534
+                                           String jsonPayload,
535
+                                           String muranoSessionId) {
536
+        HttpRequest request = HttpRequest.builder().method(method)
537
+                .endpoint(url + ":" + port)
538
+                .path(requestPath) // K8S cluster id
539
+                .header("X-Auth-Token", token)
540
+                .json(jsonPayload)
541
+                .build();
542
+        if (muranoSessionId != null) {
543
+            request.getHeaders().put("X-Configuration-Session", muranoSessionId);
544
+        }
545
+        if (jsonPayload != null) {
546
+            request = request.toBuilder().json(jsonPayload).build();
547
+        }
548
+
549
+        HttpCommand command = HttpCommand.create(request);
550
+        CloseableHttpResponse response = null;
551
+        try {
552
+            response = command.execute();
553
+        } catch(Exception ex) {
554
+            ex.printStackTrace();;
555
+            return null;
556
+        }
557
+
558
+        StringBuffer jsonString = new StringBuffer();
559
+        try {
560
+            BufferedReader br = new BufferedReader(new InputStreamReader(
561
+                    response.getEntity().getContent()));
562
+
563
+            //Print the raw output of murano api from the server
564
+            String output;
565
+
566
+            while ((output = br.readLine()) != null) {
567
+                jsonString.append(output + "\n");
568
+            }
569
+        } catch(Exception ex) {
570
+            return null;
571
+        }
572
+
573
+        return jsonString.toString();
574
+    }
575
+
576
+    private String fillTemplatesForKubernetes(Map templates,
577
+                                              String template,
578
+                                              String buildId,
579
+                                              String flavor,
580
+                                              String keypair) {
581
+        String appTemplate = template.replace("$.appConfiguration.name", "Kubernetes_" + buildId);
582
+        appTemplate = appTemplate.replace("$.appConfiguration.minionCount", this.slaveCount);
583
+        appTemplate = appTemplate.replace("$.appConfiguration.gatewayCount", this.gatewayCount);
584
+        appTemplate = appTemplate.replace("dockerRegistry", "");
585
+
586
+        // Setup KubeMaster
587
+        Map<String,Object> masterTemplate = (Map<String,Object>) templates.get("masterNode");
588
+        String kubeMasterTemplate = "{" + getApplicationJsonTemplate(masterTemplate) + "}";
589
+        kubeMasterTemplate = kubeMasterTemplate.replaceAll("$.instanceConfiguration.unitNamingPattern-.*?.com", "K8sMaster" + buildId); //Name
590
+        kubeMasterTemplate = kubeMasterTemplate.replace("$.instanceConfiguration.flavor", flavor);
591
+        kubeMasterTemplate = kubeMasterTemplate.replace("$.appConfiguration.assignFloatingIP", "True");
592
+        kubeMasterTemplate = kubeMasterTemplate.replace("$.instanceConfiguration.keyPair", keypair);
593
+        kubeMasterTemplate = kubeMasterTemplate.replace("$.instanceConfiguration.availabilityZone", "nova");
594
+        appTemplate = appTemplate.replace("\"masterNode\":\"$masterNode\"", "\"masterNode\":" +  kubeMasterTemplate);
595
+
596
+        // Kube Minion
597
+        StringBuffer sbMinionTemplate = new StringBuffer();
598
+        String delim = "";
599
+
600
+        for(int i = 1; i<= Integer.parseInt(this.slaveCount); i++) {
601
+            Map<String,Object> minionTemplate = (Map<String,Object>) templates.get("minionNode");
602
+            String kubeMinionTemplate = "{" + getApplicationJsonTemplate(minionTemplate) + "}";
603
+
604
+            kubeMinionTemplate = kubeMinionTemplate.replaceAll("\\$.instanceConfiguration.unitNamingPattern-.*?.com", "K8sMinion-" + generateUUID()); //Name
605
+            kubeMinionTemplate = kubeMinionTemplate.replace("$.instanceConfiguration.flavor", flavor);
606
+            kubeMinionTemplate = kubeMinionTemplate.replace("$.appConfiguration.assignFloatingIP", "True");
607
+            kubeMinionTemplate = kubeMinionTemplate.replace("$.instanceConfiguration.keyPair", keypair);
608
+            kubeMinionTemplate = kubeMinionTemplate.replace("$.instanceConfiguration.availabilityZone", "nova");
609
+
610
+            sbMinionTemplate.append(delim).append(kubeMinionTemplate);
611
+            delim = ",";
612
+        }
613
+        appTemplate = appTemplate.replace("\"minionNodes\":\"repeat($minionNode, $.appConfiguration.maxMinionCount)\"", "\"minionNodes\": [" + sbMinionTemplate.toString() + "]");
614
+
615
+        // Kube Gateway
616
+        StringBuffer sbGatewayTemplate = new StringBuffer();
617
+        delim = "";
618
+
619
+        for(int i = 1; i<= Integer.parseInt(this.gatewayCount); i++) {
620
+            Map<String,Object> gatewayTemplate = (Map<String,Object>) templates.get("gatewayNode");
621
+            String gatewayMinionTemplate = "{" + getApplicationJsonTemplate(gatewayTemplate ) + "}";
622
+
623
+            gatewayMinionTemplate = gatewayMinionTemplate.replaceAll("\\$.instanceConfiguration.unitNamingPattern-.*?.com", "K8sGateway-" + generateUUID()); //Name
624
+            gatewayMinionTemplate = gatewayMinionTemplate.replace("$.instanceConfiguration.flavor", flavor);
625
+            gatewayMinionTemplate = gatewayMinionTemplate.replace("$.appConfiguration.assignFloatingIP", "True");
626
+            gatewayMinionTemplate = gatewayMinionTemplate.replace("$.instanceConfiguration.keyPair", keypair);
627
+            gatewayMinionTemplate = gatewayMinionTemplate.replace("$.instanceConfiguration.availabilityZone", "nova");
628
+
629
+            sbGatewayTemplate.append(delim).append(gatewayMinionTemplate);
630
+            delim = ",";
631
+        }
632
+        appTemplate = appTemplate.replace("\"gatewayNodes\":\"repeat($gatewayNode, $.appConfiguration.maxGatewayCount)\"", "\"gatewayNodes\": [" + sbGatewayTemplate.toString() + "]");
633
+
634
+        return appTemplate;
635
+    }
636
+
637
+    private String generateUUID() {
638
+        SecureRandom ng = new SecureRandom();
639
+        long MSB = 0x8000000000000000L;
640
+
641
+        String str = Long.toHexString(MSB | ng.nextLong()) + Long.toHexString(MSB | ng.nextLong());
642
+        return str;
643
+
644
+    }
645
+
646
+    private String getApplicationJsonTemplate(Map<String,Object> template) {
647
+        String templateStr = "";
648
+        boolean firstStr = true;
649
+        for (String k : template.keySet()) {
650
+            if (!firstStr) {
651
+                templateStr += ",";
652
+            }
653
+            firstStr = false;
654
+            Object v = template.get(k);
655
+            if (v instanceof Map) {
656
+                templateStr += "\"" + k + "\": {" + this.getApplicationJsonTemplate((Map<String, Object>)v) ;
657
+                templateStr += "}";
658
+            } else if (v instanceof String) {
659
+                String uuid = generateUUID();
660
+                if (k.equals("type")) {
661
+                    templateStr += "\"" + k + "\":\"" + v + "\"";
662
+                    templateStr += ",\"id\":\"" + uuid + "\"";
663
+                }else if (k.equals("name") && (((String)v).contains("generateHostname"))) {
664
+                    templateStr += "\"name\":\"$.instanceConfiguration.unitNamingPattern-" + uuid + ".com\"";
665
+                }else {
666
+                    templateStr += "\"" + k + "\":\"" + v + "\"";
667
+                }
668
+            }
669
+        }
670
+        return templateStr;
671
+    }
672
+
673
+    @Override
674
+    public BuildStepMonitor getRequiredMonitorService() {
675
+        return BuildStepMonitor.BUILD;
676
+    }
677
+
678
+    @Extension
679
+    public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {
680
+
681
+        public DescriptorImpl() {
682
+            load();
683
+        }
684
+
685
+        @Override
686
+        public boolean isApplicable(Class<? extends AbstractProject> aClass) {
687
+            return true;
688
+        }
689
+
690
+
691
+        public FormValidation doCheckServerUrl(@QueryParameter String serverUrl) {
692
+            if (serverUrl.length() == 0)
693
+                return FormValidation.error("Please enter a server url");
694
+
695
+            if (serverUrl.indexOf("://") == -1)
696
+                return FormValidation.error("Enter a url of the format http(s)://<server>");
697
+
698
+            return FormValidation.ok();
699
+        }
700
+
701
+        public FormValidation doTestConnection(@QueryParameter("serverUrl") final String serverUrl,
702
+                                               @QueryParameter("username") final String username,
703
+                                               @QueryParameter("password") final String password,
704
+                                               @QueryParameter("tenantName") final String tenantName)
705
+                throws IOException, ServletException {
706
+            try {
707
+                OpenstackClient client = new OpenstackClient(serverUrl,
708
+                        username,
709
+                        password,
710
+                        tenantName);
711
+                if (client.authenticate()) {
712
+                    return FormValidation.ok("Success");
713
+                } else {
714
+                    return FormValidation.error("Unable to connect to server. Please check credentials");
715
+                }
716
+            } catch (Exception e) {
717
+                return FormValidation.error("Error: "+e.getMessage());
718
+            }
719
+        }
720
+
721
+        public ListBoxModel doFillFlavorItems(@QueryParameter("serverUrl") final String serverUrl,
722
+                                              @QueryParameter("username") final String username,
723
+                                              @QueryParameter("password") final String password,
724
+                                              @QueryParameter("tenantName") final String tenantName) {
725
+            ListBoxModel flavor = new ListBoxModel();
726
+            OpenstackClient client = new OpenstackClient(serverUrl,
727
+                    username,
728
+                    password,
729
+                    tenantName);
730
+            client.authenticate();      // Authenticate to Openstack
731
+            if (client.getOSClient() != null) {
732
+                for (String fl: client.getFlavors()) {
733
+                    flavor.add(fl);
734
+                }
735
+            }
736
+            return flavor;
737
+        }
738
+
739
+        public ListBoxModel doFillKeypairsItems(@QueryParameter("serverUrl") final String serverUrl,
740
+                                                @QueryParameter("username") final String username,
741
+                                                @QueryParameter("password") final String password,
742
+                                                @QueryParameter("tenantName") final String tenantName) {
743
+            ListBoxModel keypairs = new ListBoxModel();
744
+            OpenstackClient client = new OpenstackClient(serverUrl,
745
+                    username,
746
+                    password,
747
+                    tenantName);
748
+            client.authenticate();      // Authenticate to Openstack
749
+            if (client.getOSClient() != null) {
750
+                for (String fl: client.getKeypairs()) {
751
+                    keypairs.add(fl);
752
+                }
753
+            }
754
+            return keypairs;
755
+        }
756
+
757
+        /**
758
+         * This human readable name is used in the configuration screen.
759
+         */
760
+        public String getDisplayName() {
761
+            return "Murano Kubernetes Plugin";
762
+        }
763
+    }
764
+}
765
+

+ 94
- 0
kubernetes/src/main/java/com/mirantis/plugins/murano/client/OpenstackClient.java View File

@@ -0,0 +1,94 @@
1
+package com.mirantis.plugins.murano.client;
2
+
3
+import org.openstack4j.api.OSClient;
4
+import org.openstack4j.model.compute.Flavor;
5
+import org.openstack4j.model.compute.Keypair;
6
+import org.openstack4j.openstack.OSFactory;
7
+
8
+import java.util.ArrayList;
9
+import java.util.List;
10
+import java.util.logging.Level;
11
+import java.util.logging.Logger;
12
+
13
+/**
14
+ * Client Class to talk to Openstack/Murano APIs
15
+ */
16
+public class OpenstackClient {
17
+
18
+    private final static Logger LOG = Logger.getLogger(OpenstackClient.class.getName());
19
+    private String serverUrl;
20
+    private String username;
21
+    private String password;
22
+    private String tenantName;
23
+
24
+    private OSClient.OSClientV2 os = null;
25
+
26
+    public OpenstackClient(String serverUrl,
27
+                           String username,
28
+                           String password,
29
+                           String tenantName) {
30
+        this.serverUrl = serverUrl;
31
+        this.username = username;
32
+        this.password = password;
33
+        this.tenantName = tenantName;
34
+    }
35
+
36
+    /**
37
+     * Authenticate to the Openstack instance given the credentials in constructor
38
+     * @return whether the auth was successful
39
+     */
40
+    public boolean authenticate() {
41
+        boolean success = false;
42
+        try {
43
+            this.os = OSFactory.builderV2()
44
+                    .endpoint(this.serverUrl + ":5000/v2.0")
45
+                    .credentials(this.username, this.password)
46
+                    .tenantName(this.tenantName)
47
+                    .authenticate();
48
+            success = true;
49
+        } catch(Exception ex) {
50
+            LOG.log(Level.SEVERE, "Error connecting to Client", ex);
51
+            success = false;
52
+        }
53
+        return success;
54
+    }
55
+
56
+    /**
57
+     * Get all the Flavors the user has access to
58
+     * @return A List of Flavor objects
59
+     */
60
+    public ArrayList<String> getFlavors() {
61
+        ArrayList<String> flavors = new ArrayList<String>();
62
+        if (this.os != null) {
63
+            List<? extends Flavor> flavorsList = this.os.compute().flavors().list();
64
+            for (Flavor f : flavorsList) {
65
+                flavors.add(f.getName());
66
+            }
67
+        }
68
+        return flavors;
69
+
70
+    }
71
+
72
+    /**
73
+     * Get all the Keypairs the user has access to
74
+     * @return A List of Keypair object
75
+     */
76
+    public ArrayList<String> getKeypairs() {
77
+        ArrayList<String> keypairs = new ArrayList<String>();
78
+        if (this.os != null) {
79
+            List<? extends Keypair> kpList = this.os.compute().keypairs().list();
80
+            for (Keypair k : kpList) {
81
+                keypairs.add(k.getName());
82
+            }
83
+        }
84
+        return keypairs;
85
+    }
86
+
87
+    /**
88
+     * Helper object to return the OSClient
89
+     * @return OSClient V2
90
+     */
91
+    public OSClient.OSClientV2 getOSClient() {
92
+        return this.os;
93
+    }
94
+}

+ 59
- 0
kubernetes/src/main/resources/com/mirantis/plugins/murano/MuranoBuilder/config.jelly View File

@@ -0,0 +1,59 @@
1
+<?jelly escape-by-default='true'?>
2
+<j:jelly xmlns:j="jelly:core"
3
+         xmlns:st="jelly:stapler"
4
+         xmlns:d="jelly:define"
5
+         xmlns:l="/lib/layout"
6
+         xmlns:t="/lib/hudson"
7
+         xmlns:f="/lib/form">
8
+
9
+  <f:section title="Server Credentials">
10
+      <f:entry title="Server URL" field="serverUrl">
11
+        <f:textbox />
12
+      </f:entry>
13
+      <f:entry title="Username" field="username">
14
+        <f:textbox />
15
+      </f:entry>
16
+      <f:entry title="Password" field="password">
17
+        <f:password />
18
+      </f:entry>
19
+      <f:entry title="Tenant Name" field="tenantName">
20
+        <f:textbox />
21
+      </f:entry>
22
+      <f:validateButton
23
+         title="Test Connection" progress="Connecting..."
24
+         method="testConnection" with="serverUrl,username,password,tenantName" />
25
+  </f:section>
26
+
27
+  <f:section title="Node Details">
28
+    <f:entry title="Kubernetes Cluster Name" field="clusterName">
29
+        <f:textbox />
30
+    </f:entry>
31
+    <f:entry title="Docker Registry" field="dockerRegistry">
32
+        <f:textbox />
33
+    </f:entry>
34
+    <f:entry title="Flavor List" field="flavor">
35
+        <f:select />
36
+    </f:entry>
37
+    <f:entry title="Keypair" field="keypairs">
38
+        <f:select />
39
+     </f:entry>
40
+  </f:section>
41
+
42
+  <f:section title="Additional Node Details">
43
+    <f:advanced>
44
+        <f:entry title="Number of Slave Nodes" field="slaveCount">
45
+            <f:textbox default="1"/>
46
+        </f:entry>
47
+        <f:entry title="Number of Gateway Nodes" field="gatewayCount">
48
+            <f:textbox default="1"/>
49
+        </f:entry>
50
+    </f:advanced>
51
+  </f:section>
52
+
53
+  <f:section title="Image Details">
54
+      <f:entry title="Docker Image" field="dockerImage">
55
+          <f:textbox />
56
+      </f:entry>
57
+  </f:section>
58
+
59
+</j:jelly>

+ 7
- 0
kubernetes/src/main/resources/index.jelly View File

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

Loading…
Cancel
Save