blob: e8ddd2f1b3502dff664f61a1933833d6fc271108 [file] [log] [blame]
Sree Kuchibhotla7b739662015-11-05 10:28:16 -08001#!/usr/bin/env python2.7
Sree Kuchibhotlae1dd18a2016-02-29 13:28:55 -08002# Copyright 2015-2016, Google Inc.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -08003# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31import requests
32import json
33
sreekbfe37a82015-11-16 18:57:33 -080034_REQUEST_TIMEOUT_SECS = 10
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080035
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080036
sreekbfe37a82015-11-16 18:57:33 -080037def _make_pod_config(pod_name, image_name, container_port_list, cmd_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080038 arg_list, env_dict):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080039 """Creates a string containing the Pod defintion as required by the Kubernetes API"""
40 body = {
41 'kind': 'Pod',
42 'apiVersion': 'v1',
43 'metadata': {
44 'name': pod_name,
45 'labels': {'name': pod_name}
46 },
47 'spec': {
48 'containers': [
49 {
50 'name': pod_name,
51 'image': image_name,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080052 'ports': [{'containerPort': port,
Sree Kuchibhotla61c134f2016-02-26 11:03:29 -080053 'protocol': 'TCP'}
54 for port in container_port_list],
Sree Kuchibhotla559e45b2016-02-19 03:02:16 -080055 'imagePullPolicy': 'Always'
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080056 }
57 ]
58 }
59 }
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080060
61 env_list = [{'name': k, 'value': v} for (k, v) in env_dict.iteritems()]
62 if len(env_list) > 0:
63 body['spec']['containers'][0]['env'] = env_list
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080064
65 # Add the 'Command' and 'Args' attributes if they are passed.
66 # Note:
67 # - 'Command' overrides the ENTRYPOINT in the Docker Image
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080068 # - 'Args' override the CMD in Docker image (yes, it is confusing!)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080069 if len(cmd_list) > 0:
70 body['spec']['containers'][0]['command'] = cmd_list
71 if len(arg_list) > 0:
72 body['spec']['containers'][0]['args'] = arg_list
73 return json.dumps(body)
74
75
sreekbfe37a82015-11-16 18:57:33 -080076def _make_service_config(service_name, pod_name, service_port_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -080077 container_port_list, is_headless):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080078 """Creates a string containing the Service definition as required by the Kubernetes API.
79
80 NOTE:
sreekbfe37a82015-11-16 18:57:33 -080081 This creates either a Headless Service or 'LoadBalancer' service depending on
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080082 the is_headless parameter. For Headless services, there is no 'type' attribute
83 and the 'clusterIP' attribute is set to 'None'. Also, if the service is
84 Headless, Kubernetes creates DNS entries for Pods - i.e creates DNS A-records
85 mapping the service's name to the Pods' IPs
86 """
87 if len(container_port_list) != len(service_port_list):
88 print(
89 'ERROR: container_port_list and service_port_list must be of same size')
90 return ''
91 body = {
92 'kind': 'Service',
93 'apiVersion': 'v1',
94 'metadata': {
95 'name': service_name,
96 'labels': {
97 'name': service_name
98 }
99 },
100 'spec': {
101 'ports': [],
102 'selector': {
103 'name': pod_name
104 }
105 }
106 }
107 # Populate the 'ports' list in the 'spec' section. This maps service ports
108 # (port numbers that are exposed by Kubernetes) to container ports (i.e port
109 # numbers that are exposed by your Docker image)
110 for idx in range(len(container_port_list)):
111 port_entry = {
112 'port': service_port_list[idx],
113 'targetPort': container_port_list[idx],
114 'protocol': 'TCP'
115 }
116 body['spec']['ports'].append(port_entry)
117
118 # Make this either a LoadBalancer service or a headless service depending on
119 # the is_headless parameter
120 if is_headless:
121 body['spec']['clusterIP'] = 'None'
122 else:
123 body['spec']['type'] = 'LoadBalancer'
124 return json.dumps(body)
125
126
sreekbfe37a82015-11-16 18:57:33 -0800127def _print_connection_error(msg):
Sree Kuchibhotla6a7bd162015-11-17 11:46:04 -0800128 print('ERROR: Connection failed. Did you remember to run Kubenetes proxy on '
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800129 'localhost (i.e kubectl proxy --port=<proxy_port>) ?. Error: %s' % msg)
130
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800131
sreekbfe37a82015-11-16 18:57:33 -0800132def _do_post(post_url, api_name, request_body):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800133 """Helper to do HTTP POST.
134
135 Note:
136 1) On success, Kubernetes returns a success code of 201(CREATED) not 200(OK)
137 2) A response code of 509(CONFLICT) is interpreted as a success code (since
138 the error is most likely due to the resource already existing). This makes
sreekbfe37a82015-11-16 18:57:33 -0800139 _do_post() idempotent which is semantically desirable.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800140 """
141 is_success = True
142 try:
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800143 r = requests.post(post_url,
144 data=request_body,
145 timeout=_REQUEST_TIMEOUT_SECS)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800146 if r.status_code == requests.codes.conflict:
147 print('WARN: Looks like the resource already exists. Api: %s, url: %s' %
148 (api_name, post_url))
sreekbfe37a82015-11-16 18:57:33 -0800149 elif r.status_code != requests.codes.created:
150 print('ERROR: %s API returned error. HTTP response: (%d) %s' %
151 (api_name, r.status_code, r.text))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800152 is_success = False
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800153 except (requests.exceptions.Timeout,
154 requests.exceptions.ConnectionError) as e:
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800155 is_success = False
sreekbfe37a82015-11-16 18:57:33 -0800156 _print_connection_error(str(e))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800157 return is_success
158
159
sreekbfe37a82015-11-16 18:57:33 -0800160def _do_delete(del_url, api_name):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800161 """Helper to do HTTP DELETE.
162
163 Note: A response code of 404(NOT_FOUND) is treated as success to keep
sreekbfe37a82015-11-16 18:57:33 -0800164 _do_delete() idempotent.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800165 """
166 is_success = True
167 try:
sreekbfe37a82015-11-16 18:57:33 -0800168 r = requests.delete(del_url, timeout=_REQUEST_TIMEOUT_SECS)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800169 if r.status_code == requests.codes.not_found:
170 print('WARN: The resource does not exist. Api: %s, url: %s' %
171 (api_name, del_url))
172 elif r.status_code != requests.codes.ok:
173 print('ERROR: %s API returned error. HTTP response: %s' %
174 (api_name, r.text))
175 is_success = False
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800176 except (requests.exceptions.Timeout,
177 requests.exceptions.ConnectionError) as e:
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800178 is_success = False
sreekbfe37a82015-11-16 18:57:33 -0800179 _print_connection_error(str(e))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800180 return is_success
181
182
183def create_service(kube_host, kube_port, namespace, service_name, pod_name,
184 service_port_list, container_port_list, is_headless):
sreekbfe37a82015-11-16 18:57:33 -0800185 """Creates either a Headless Service or a LoadBalancer Service depending
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800186 on the is_headless parameter.
187 """
188 post_url = 'http://%s:%d/api/v1/namespaces/%s/services' % (
189 kube_host, kube_port, namespace)
sreekbfe37a82015-11-16 18:57:33 -0800190 request_body = _make_service_config(service_name, pod_name, service_port_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800191 container_port_list, is_headless)
sreekbfe37a82015-11-16 18:57:33 -0800192 return _do_post(post_url, 'Create Service', request_body)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800193
194
195def create_pod(kube_host, kube_port, namespace, pod_name, image_name,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800196 container_port_list, cmd_list, arg_list, env_dict):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800197 """Creates a Kubernetes Pod.
198
199 Note that it is generally NOT considered a good practice to directly create
200 Pods. Typically, the recommendation is to create 'Controllers' to create and
201 manage Pods' lifecycle. Currently Kubernetes only supports 'Replication
202 Controller' which creates a configurable number of 'identical Replicas' of
203 Pods and automatically restarts any Pods in case of failures (for eg: Machine
204 failures in Kubernetes). This makes it less flexible for our test use cases
sreekbfe37a82015-11-16 18:57:33 -0800205 where we might want slightly different set of args to each Pod. Hence we
206 directly create Pods and not care much about Kubernetes failures since those
207 are very rare.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800208 """
209 post_url = 'http://%s:%d/api/v1/namespaces/%s/pods' % (kube_host, kube_port,
210 namespace)
sreekbfe37a82015-11-16 18:57:33 -0800211 request_body = _make_pod_config(pod_name, image_name, container_port_list,
Sree Kuchibhotla44ca2c22016-02-16 09:48:36 -0800212 cmd_list, arg_list, env_dict)
Sree Kuchibhotla3abacfb2015-11-17 11:42:43 -0800213 return _do_post(post_url, 'Create Pod', request_body)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800214
215
216def delete_service(kube_host, kube_port, namespace, service_name):
217 del_url = 'http://%s:%d/api/v1/namespaces/%s/services/%s' % (
218 kube_host, kube_port, namespace, service_name)
sreekbfe37a82015-11-16 18:57:33 -0800219 return _do_delete(del_url, 'Delete Service')
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800220
221
222def delete_pod(kube_host, kube_port, namespace, pod_name):
223 del_url = 'http://%s:%d/api/v1/namespaces/%s/pods/%s' % (kube_host, kube_port,
224 namespace, pod_name)
sreekbfe37a82015-11-16 18:57:33 -0800225 return _do_delete(del_url, 'Delete Pod')
Sree Kuchibhotla61c134f2016-02-26 11:03:29 -0800226
227
228def create_pod_and_service(kube_host, kube_port, namespace, pod_name,
229 image_name, container_port_list, cmd_list, arg_list,
230 env_dict, is_headless_service):
Sree Kuchibhotlada25fdb2016-02-26 13:41:06 -0800231 """A helper function that creates a pod and a service (if pod creation was successful)."""
Sree Kuchibhotla61c134f2016-02-26 11:03:29 -0800232 is_success = create_pod(kube_host, kube_port, namespace, pod_name, image_name,
233 container_port_list, cmd_list, arg_list, env_dict)
234 if not is_success:
235 print 'Error in creating Pod'
236 return False
237
238 is_success = create_service(
239 kube_host,
240 kube_port,
241 namespace,
242 pod_name, # Use pod_name for service
243 pod_name,
244 container_port_list, # Service port list same as container port list
245 container_port_list,
246 is_headless_service)
247 if not is_success:
248 print 'Error in creating Service'
249 return False
250
251 print 'Successfully created the pod/service %s' % pod_name
252 return True
253
254
255def delete_pod_and_service(kube_host, kube_port, namespace, pod_name):
Sree Kuchibhotlada25fdb2016-02-26 13:41:06 -0800256 """ A helper function that calls delete_pod and delete_service """
Sree Kuchibhotla61c134f2016-02-26 11:03:29 -0800257 is_success = delete_pod(kube_host, kube_port, namespace, pod_name)
258 if not is_success:
259 print 'Error in deleting pod %s' % pod_name
260 return False
261
262 # Note: service name assumed to the the same as pod name
263 is_success = delete_service(kube_host, kube_port, namespace, pod_name)
264 if not is_success:
265 print 'Error in deleting service %s' % pod_name
266 return False
267
268 print 'Successfully deleted the Pod/Service: %s' % pod_name
269 return True