blob: 7dd30153650383a5669e07cb82283f226b7ec968 [file] [log] [blame]
Sree Kuchibhotla7b739662015-11-05 10:28:16 -08001#!/usr/bin/env python2.7
2# Copyright 2015, Google Inc.
3# 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
sreekbfe37a82015-11-16 18:57:33 -080036def _make_pod_config(pod_name, image_name, container_port_list, cmd_list,
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080037 arg_list):
38 """Creates a string containing the Pod defintion as required by the Kubernetes API"""
39 body = {
40 'kind': 'Pod',
41 'apiVersion': 'v1',
42 'metadata': {
43 'name': pod_name,
44 'labels': {'name': pod_name}
45 },
46 'spec': {
47 'containers': [
48 {
49 'name': pod_name,
50 'image': image_name,
51 'ports': []
52 }
53 ]
54 }
55 }
56 # Populate the 'ports' list
57 for port in container_port_list:
58 port_entry = {'containerPort': port, 'protocol': 'TCP'}
59 body['spec']['containers'][0]['ports'].append(port_entry)
60
61 # Add the 'Command' and 'Args' attributes if they are passed.
62 # Note:
63 # - 'Command' overrides the ENTRYPOINT in the Docker Image
64 # - 'Args' override the COMMAND in Docker image (yes, it is confusing!)
65 if len(cmd_list) > 0:
66 body['spec']['containers'][0]['command'] = cmd_list
67 if len(arg_list) > 0:
68 body['spec']['containers'][0]['args'] = arg_list
69 return json.dumps(body)
70
71
sreekbfe37a82015-11-16 18:57:33 -080072def _make_service_config(service_name, pod_name, service_port_list,
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080073 container_port_list, is_headless):
74 """Creates a string containing the Service definition as required by the Kubernetes API.
75
76 NOTE:
sreekbfe37a82015-11-16 18:57:33 -080077 This creates either a Headless Service or 'LoadBalancer' service depending on
Sree Kuchibhotla7b739662015-11-05 10:28:16 -080078 the is_headless parameter. For Headless services, there is no 'type' attribute
79 and the 'clusterIP' attribute is set to 'None'. Also, if the service is
80 Headless, Kubernetes creates DNS entries for Pods - i.e creates DNS A-records
81 mapping the service's name to the Pods' IPs
82 """
83 if len(container_port_list) != len(service_port_list):
84 print(
85 'ERROR: container_port_list and service_port_list must be of same size')
86 return ''
87 body = {
88 'kind': 'Service',
89 'apiVersion': 'v1',
90 'metadata': {
91 'name': service_name,
92 'labels': {
93 'name': service_name
94 }
95 },
96 'spec': {
97 'ports': [],
98 'selector': {
99 'name': pod_name
100 }
101 }
102 }
103 # Populate the 'ports' list in the 'spec' section. This maps service ports
104 # (port numbers that are exposed by Kubernetes) to container ports (i.e port
105 # numbers that are exposed by your Docker image)
106 for idx in range(len(container_port_list)):
107 port_entry = {
108 'port': service_port_list[idx],
109 'targetPort': container_port_list[idx],
110 'protocol': 'TCP'
111 }
112 body['spec']['ports'].append(port_entry)
113
114 # Make this either a LoadBalancer service or a headless service depending on
115 # the is_headless parameter
116 if is_headless:
117 body['spec']['clusterIP'] = 'None'
118 else:
119 body['spec']['type'] = 'LoadBalancer'
120 return json.dumps(body)
121
122
sreekbfe37a82015-11-16 18:57:33 -0800123def _print_connection_error(msg):
Sree Kuchibhotla6a7bd162015-11-17 11:46:04 -0800124 print('ERROR: Connection failed. Did you remember to run Kubenetes proxy on '
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800125 'localhost (i.e kubectl proxy --port=<proxy_port>) ?. Error: %s' % msg)
126
sreekbfe37a82015-11-16 18:57:33 -0800127def _do_post(post_url, api_name, request_body):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800128 """Helper to do HTTP POST.
129
130 Note:
131 1) On success, Kubernetes returns a success code of 201(CREATED) not 200(OK)
132 2) A response code of 509(CONFLICT) is interpreted as a success code (since
133 the error is most likely due to the resource already existing). This makes
sreekbfe37a82015-11-16 18:57:33 -0800134 _do_post() idempotent which is semantically desirable.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800135 """
136 is_success = True
137 try:
sreekbfe37a82015-11-16 18:57:33 -0800138 r = requests.post(post_url, data=request_body, timeout=_REQUEST_TIMEOUT_SECS)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800139 if r.status_code == requests.codes.conflict:
140 print('WARN: Looks like the resource already exists. Api: %s, url: %s' %
141 (api_name, post_url))
sreekbfe37a82015-11-16 18:57:33 -0800142 elif r.status_code != requests.codes.created:
143 print('ERROR: %s API returned error. HTTP response: (%d) %s' %
144 (api_name, r.status_code, r.text))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800145 is_success = False
146 except(requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
147 is_success = False
sreekbfe37a82015-11-16 18:57:33 -0800148 _print_connection_error(str(e))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800149 return is_success
150
151
sreekbfe37a82015-11-16 18:57:33 -0800152def _do_delete(del_url, api_name):
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800153 """Helper to do HTTP DELETE.
154
155 Note: A response code of 404(NOT_FOUND) is treated as success to keep
sreekbfe37a82015-11-16 18:57:33 -0800156 _do_delete() idempotent.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800157 """
158 is_success = True
159 try:
sreekbfe37a82015-11-16 18:57:33 -0800160 r = requests.delete(del_url, timeout=_REQUEST_TIMEOUT_SECS)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800161 if r.status_code == requests.codes.not_found:
162 print('WARN: The resource does not exist. Api: %s, url: %s' %
163 (api_name, del_url))
164 elif r.status_code != requests.codes.ok:
165 print('ERROR: %s API returned error. HTTP response: %s' %
166 (api_name, r.text))
167 is_success = False
168 except(requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
169 is_success = False
sreekbfe37a82015-11-16 18:57:33 -0800170 _print_connection_error(str(e))
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800171 return is_success
172
173
174def create_service(kube_host, kube_port, namespace, service_name, pod_name,
175 service_port_list, container_port_list, is_headless):
sreekbfe37a82015-11-16 18:57:33 -0800176 """Creates either a Headless Service or a LoadBalancer Service depending
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800177 on the is_headless parameter.
178 """
179 post_url = 'http://%s:%d/api/v1/namespaces/%s/services' % (
180 kube_host, kube_port, namespace)
sreekbfe37a82015-11-16 18:57:33 -0800181 request_body = _make_service_config(service_name, pod_name, service_port_list,
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800182 container_port_list, is_headless)
sreekbfe37a82015-11-16 18:57:33 -0800183 return _do_post(post_url, 'Create Service', request_body)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800184
185
186def create_pod(kube_host, kube_port, namespace, pod_name, image_name,
187 container_port_list, cmd_list, arg_list):
188 """Creates a Kubernetes Pod.
189
190 Note that it is generally NOT considered a good practice to directly create
191 Pods. Typically, the recommendation is to create 'Controllers' to create and
192 manage Pods' lifecycle. Currently Kubernetes only supports 'Replication
193 Controller' which creates a configurable number of 'identical Replicas' of
194 Pods and automatically restarts any Pods in case of failures (for eg: Machine
195 failures in Kubernetes). This makes it less flexible for our test use cases
sreekbfe37a82015-11-16 18:57:33 -0800196 where we might want slightly different set of args to each Pod. Hence we
197 directly create Pods and not care much about Kubernetes failures since those
198 are very rare.
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800199 """
200 post_url = 'http://%s:%d/api/v1/namespaces/%s/pods' % (kube_host, kube_port,
201 namespace)
sreekbfe37a82015-11-16 18:57:33 -0800202 request_body = _make_pod_config(pod_name, image_name, container_port_list,
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800203 cmd_list, arg_list)
Sree Kuchibhotla3abacfb2015-11-17 11:42:43 -0800204 return _do_post(post_url, 'Create Pod', request_body)
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800205
206
207def delete_service(kube_host, kube_port, namespace, service_name):
208 del_url = 'http://%s:%d/api/v1/namespaces/%s/services/%s' % (
209 kube_host, kube_port, namespace, service_name)
sreekbfe37a82015-11-16 18:57:33 -0800210 return _do_delete(del_url, 'Delete Service')
Sree Kuchibhotla7b739662015-11-05 10:28:16 -0800211
212
213def delete_pod(kube_host, kube_port, namespace, pod_name):
214 del_url = 'http://%s:%d/api/v1/namespaces/%s/pods/%s' % (kube_host, kube_port,
215 namespace, pod_name)
sreekbfe37a82015-11-16 18:57:33 -0800216 return _do_delete(del_url, 'Delete Pod')