class K8sReplicationController(K8sPodBasedObject): def __init__(self, config=None, name=None, image=None, replicas=0): super(K8sReplicationController, self).__init__(config=config, obj_type='ReplicationController', name=name) self.model = ReplicationController(name=name, namespace=self.config.namespace) self.set_replicas(replicas) rc_version = str(uuid.uuid4()) self.model.add_pod_label(k='rc_version', v=rc_version) selector = {'name': name, 'rc_version': rc_version} self.set_selector(selector) if image is not None: container = K8sContainer(name=name, image=image) self.add_container(container) self.model.set_pod_name(name=name) if self.config.pull_secret is not None: self.add_image_pull_secrets(name=self.config.pull_secret) # ------------------------------------------------------------------------------------- override def create(self): super(K8sReplicationController, self).create() self.get() self.wait_for_replicas(self.get_replicas()) return self def update(self): super(K8sReplicationController, self).update() self.get() return self # ------------------------------------------------------------------------------------- add def add_annotation(self, k=None, v=None): self.model.add_annotation(k=k, v=v) return self def add_label(self, k=None, v=None): self.model.add_label(k=k, v=v) return self def add_pod_annotation(self, k=None, v=None): self.model.add_pod_annotation(k=k, v=v) return self def add_pod_label(self, k=None, v=None): self.model.add_pod_label(k=k, v=v) return self # ------------------------------------------------------------------------------------- del def del_annotation(self, k=None): self.model.del_annotation(k=k) return self def del_label(self, k=None): self.model.del_label(k=k) return self def del_pod_annotation(self, k=None): self.model.del_pod_annotation(k=k) return self def del_pod_label(self, k=None): self.model.del_pod_label(k=k) return self # ------------------------------------------------------------------------------------- get def get(self): self.model = ReplicationController(model=self.get_model()) return self def get_annotation(self, k=None): return self.model.get_annotation(k=k) def get_annotations(self): return self.model.get_annotations() def get_label(self, k=None): return self.model.get_label(k=k) def get_labels(self): return self.model.get_labels() def get_namespace(self): return self.model.get_namespace() def get_pod_annotation(self, k=None): return self.model.get_pod_annotation(k=k) def get_pod_annotations(self): return self.model.get_pod_annotations() def get_pod_label(self, k=None): return self.model.get_pod_label(k=k) def get_pod_labels(self): return self.model.get_pod_labels() def get_replicas(self): return self.model.get_replicas() def get_selector(self): return self.model.get_selector() # ------------------------------------------------------------------------------------- set def set_annotations(self, annotations=None): self.model.set_annotations(dico=annotations) return self def set_labels(self, labels=None): self.model.set_labels(dico=labels) return self def set_namespace(self, name=None): self.model.set_namespace(name=name) return self def set_pod_annotations(self, annotations=None): self.model.set_pod_annotations(annotations=annotations) return self def set_pod_labels(self, labels=None): self.model.set_pod_labels(labels=labels) return self def set_replicas(self, replicas=None): self.model.set_replicas(replicas=replicas) return self def set_selector(self, selector=None): self.model.set_selector(dico=selector) return self # ------------------------------------------------------------------------------------- wait for replicas def wait_for_replicas(self, replicas=None, labels=None): if replicas is None: raise SyntaxError('ReplicationController: replicas: [ {0} ] cannot be None.'.format(replicas)) if not isinstance(replicas, int) or replicas < 0: raise SyntaxError('ReplicationController: replicas: [ {0} ] must be a positive integer.'.format(replicas)) if labels is None: labels = self.get_pod_labels() name = labels.get('name', None) pod_list = list() pod_qty = len(pod_list) ready_check = False start_time = time.time() print('Waiting for replicas to scale to: [ {0} ] with labels: [ {1} ]'.format(replicas, labels)) while not ((pod_qty == replicas) and ready_check): pod_list = self._get_pods(name=name, labels=labels) pod_qty = len(pod_list) if replicas > 0: pods_ready = 0 for pod in pod_list: assert isinstance(pod, K8sPod) try: if pod.is_ready(): pods_ready += 1 except NotFoundException: # while scaling down pass if pods_ready >= len(pod_list): ready_check = True else: ready_check = True elapsed_time = time.time() - start_time if elapsed_time >= SCALE_WAIT_TIMEOUT_SECONDS: # timeout raise TimedOutException("Timed out scaling replicas to: [ {0} ] with labels: [ {1} ]".format(replicas, labels)) time.sleep(0.2) return self def _get_pods(self, name=None, labels=None): if labels is None: return K8sPod.get_by_name(config=self.config, name=name) else: return K8sPod.get_by_labels(config=self.config, labels=labels) # ------------------------------------------------------------------------------------- get by name @staticmethod def get_by_name(config=None, name=None): if name is None: raise SyntaxError('ReplicationController: name: [ {0} ] cannot be None.'.format(name)) if not isinstance(name, str): raise SyntaxError('ReplicationController: name: [ {0} ] must be a string.'.format(name)) if config is not None and not isinstance(config, K8sConfig): raise SyntaxError('ReplicationController: config: [ {0} ] must be a K8sConfig'.format(config)) rc_list = list() data = {'labelSelector': 'name={0}'.format(name)} rcs = K8sReplicationController(config=config, name=name).get_with_params(data=data) for rc in rcs: try: rc_name = ReplicationController(model=rc).get_name() rc_list.append(K8sReplicationController(config=config, name=rc_name).get()) except NotFoundException: pass return rc_list # ------------------------------------------------------------------------------------- resize @staticmethod def scale(config=None, name=None, replicas=None): if name is None: raise SyntaxError('ReplicationController: name: [ {0} ] cannot be None.'.format(name)) if replicas is None: raise SyntaxError('ReplicationController: replicas: [ {0} ] cannot be None.'.format(replicas)) if not isinstance(name, str): raise SyntaxError('ReplicationController: name: [ {0} ] must be a string.'.format(name)) if not isinstance(replicas, int) or replicas < 0: raise SyntaxError('ReplicationController: replicas: [ {0} ] must be a positive integer.'.format(replicas)) if config is not None and not isinstance(config, K8sConfig): raise SyntaxError('ReplicationController: config: [ {0} ] must be a K8sConfig'.format(config)) current_rc = K8sReplicationController(config=config, name=name).get() current_rc.set_replicas(replicas) current_rc.update() current_rc.wait_for_replicas(replicas=replicas) return current_rc # ------------------------------------------------------------------------------------- rolling update @staticmethod def rolling_update(config=None, name=None, image=None, container_name=None, rc_new=None, wait_seconds=10): """ Performs a simple rolling update of a ReplicationController. See https://github.com/kubernetes/kubernetes/blob/release-1.0/docs/design/simple-rolling-update.md for algorithm details. :param config: An instance of K8sConfig. If omitted, reads from ~/.kube/config. :param name: The name of the ReplicationController we want to update. :param image: The updated image version we want applied. :param container_name: The name of the container we're targeting for the update. Required if more than one container is present. :param rc_new: An instance of K8sReplicationController with the new configuration to apply. Mutually exclusive with [image, container_name] if specified. :param wait_seconds: :return: """ if name is None: raise SyntaxError('K8sReplicationController: name: [ {0} ] cannot be None.'.format(name)) if image is None and rc_new is None: raise SyntaxError("K8sReplicationController: please specify either 'image' or 'rc_new'") if name is not None and image is not None and rc_new is not None: raise SyntaxError('K8sReplicationController: rc_new is mutually exclusive with a [image, container_name] pair.') phase = 'init' ann_update_partner = 'update-partner' ann_desired_replicas = 'desired-replicas' name_next = "{0}-next".format(name) rc_current = None rc_next = None rc_current_exists = False rc_next_exists = False try: rc_current = K8sReplicationController(config=config, name=name).get() rc_current_exists = True except NotFoundException: pass try: rc_next = K8sReplicationController(config=config, name=name_next).get() rc_next_exists = True except NotFoundException: pass if not rc_current_exists and not rc_next_exists: raise NotFoundException('K8sReplicationController: rc: [ {0} ] does not exist.'.format(name)) if rc_current_exists and not rc_next_exists: if rc_new is not None: rc_next = rc_new rc_next.add_annotation(k=ann_desired_replicas, v=str(rc_current.get_replicas())) else: rc_next = copy.deepcopy(rc_current) rc_next.add_annotation(k=ann_desired_replicas, v=str(rc_current.get_replicas())) if len(rc_next.model.pod_spec.containers) > 1 and not container_name: raise UnprocessableEntityException('K8sReplicationController: unable to determine on which container to perform a rolling_update; please specify the target container_name.') if len(rc_next.model.pod_spec.containers) == 1 and not container_name: container_name = rc_next.model.pod_spec.containers[0].model['name'] rc_next.set_container_image(name=container_name, image=image) my_version = str(uuid.uuid4()) rc_next.set_name(name=name_next) rc_next.add_pod_label(k='name', v=name) rc_next.add_pod_label(k='rc_version', v=my_version) rc_next.set_selector(selector=dict(name=name, rc_version=my_version)) rc_next.set_replicas(replicas=0) rc_next.set_pod_generate_name(mode=True, name=name) rc_next.create() rc_current.add_annotation(k=ann_update_partner, v=name_next) rc_current.update() phase = 'rollout' elif rc_next_exists and not rc_current_exists: phase = 'rename' elif rc_current_exists and rc_next_exists: if not rc_next.get_annotation(k=ann_desired_replicas): rc_next.add_annotation(k=ann_desired_replicas, v=rc_current.get_replicas()) rc_next.update() phase = 'rollout' if phase == 'rollout': desired_replicas = rc_next.get_annotation(k=ann_desired_replicas) while rc_next.get_replicas() < int(desired_replicas): next_replicas = rc_next.get_replicas() + 1 rc_next.set_replicas(replicas=next_replicas) rc_next.update() rc_next.wait_for_replicas(replicas=next_replicas, labels=rc_next.get_pod_labels()) time.sleep(wait_seconds) if rc_current.get_replicas() > 0: current_replicas = rc_current.get_replicas() - 1 rc_current.set_replicas(replicas=current_replicas) rc_current.update() rc_current.wait_for_replicas(replicas=current_replicas, labels=rc_current.get_pod_labels()) if rc_current.get_replicas() > 0: rc_current.set_replicas(replicas=0) rc_current.update() rc_current.wait_for_replicas(replicas=0, labels=rc_current.get_pod_labels()) phase = 'rename' if phase == 'rename': rc_current.delete() rc_current = copy.deepcopy(rc_next) rc_current.set_name(name=name) rc_current.del_annotation(k=ann_update_partner) rc_current.del_annotation(k=ann_desired_replicas) rc_current.create() rc_next.delete() return rc_current