Пример #1
0
    def annotate(self, path, annotations, resource_version=None):
        """Pushes a resource annotation to the K8s API resource

        The annotate operation is made with a PATCH HTTP request of kind:
        application/merge-patch+json as described in:

        https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#patch-operations  # noqa
        """
        LOG.debug("Annotate %(path)s: %(names)s", {
            'path': path,
            'names': list(annotations)
        })

        content_type = 'application/merge-patch+json'
        url, header = self._get_url_and_header(path, content_type)

        while itertools.count(1):
            metadata = {"annotations": annotations}
            if resource_version:
                metadata['resourceVersion'] = resource_version
            data = jsonutils.dumps({"metadata": metadata}, sort_keys=True)
            response = requests.patch(url,
                                      data=data,
                                      headers=header,
                                      cert=self.cert,
                                      verify=self.verify_server)
            if response.ok:
                return response.json()['metadata']['annotations']
            if response.status_code == requests.codes.conflict:
                resource = self.get(path)
                new_version = resource['metadata']['resourceVersion']
                retrieved_annotations = resource['metadata'].get(
                    'annotations', {})

                for k, v in annotations.items():
                    if v != retrieved_annotations.get(k, v):
                        break
                else:
                    # No conflicting annotations found. Retry patching
                    resource_version = new_version
                    continue
                LOG.debug(
                    "Annotations for %(path)s already present: "
                    "%(names)s", {
                        'path': path,
                        'names': retrieved_annotations
                    })

            LOG.error(
                "Exception response, headers: %(headers)s, "
                "content: %(content)s, text: %(text)s" % {
                    'headers': response.headers,
                    'content': response.content,
                    'text': response.text
                })

            if response.status_code == requests.codes.not_found:
                raise exc.K8sResourceNotFound(response.text)
            else:
                raise exc.K8sClientException(response.text)
Пример #2
0
    def test_get_exception_on_update_crd(self, ged, k8s, get_project, get_sg,
                                         get_subnets, request_vif, update_crd,
                                         release_vif):
        ged.return_value = [self._driver]
        kp = kuryrport.KuryrPortHandler()
        kp.k8s.get.return_value = self._pod
        get_sg.return_value = self._security_groups
        get_project.return_value = self._project_id
        get_subnets.return_value = mock.sentinel.subnets
        request_vif.return_value = self._vif1
        update_crd.side_effect = k_exc.K8sClientException()

        self.assertTrue(kp.get_vifs(self._kp))

        kp.k8s.get.assert_called_once_with(self._pod_uri)
        get_project.assert_called_once_with(self._pod)
        get_sg.assert_called_once_with(self._pod, self._project_id)
        get_subnets.assert_called_once_with(self._pod, self._project_id)
        request_vif.assert_called_once_with(self._pod, self._project_id,
                                            mock.sentinel.subnets,
                                            self._security_groups)
        update_crd.assert_called_once_with(
            self._kp,
            {constants.DEFAULT_IFNAME: {
                'default': True,
                'vif': self._vif1
            }})
        release_vif.assert_called_once_with(self._pod, self._vif1,
                                            self._project_id,
                                            self._security_groups)
Пример #3
0
 def get(self, path):
     LOG.debug("Get %(path)s", {'path': path})
     url = self._base_url + path
     response = requests.get(url)
     if not response.ok:
         raise exc.K8sClientException(response.text)
     return response.json()
Пример #4
0
    def annotate(self, path, annotations, resource_version=None):
        """Pushes a resource annotation to the K8s API resource

        The annotate operation is made with a PATCH HTTP request of kind:
        application/merge-patch+json as described in:

        https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#patch-operations  # noqa
        """
        LOG.debug("Annotate %(path)s: %(names)s", {
            'path': path,
            'names': list(annotations)
        })
        url = self._base_url + path
        header = {
            'Content-Type': 'application/merge-patch+json',
            'Accept': 'application/json'
        }
        if self.token:
            header.update({'Authorization': 'Bearer %s' % self.token})
        while itertools.count(1):
            data = jsonutils.dumps(
                {
                    "metadata": {
                        "annotations": annotations,
                        "resourceVersion": resource_version,
                    }
                },
                sort_keys=True)
            response = requests.patch(url,
                                      data=data,
                                      headers=header,
                                      cert=self.cert,
                                      verify=self.verify_server)
            if response.ok:
                return response.json()['metadata']['annotations']
            if response.status_code == requests.codes.conflict:
                resource = self.get(path)
                new_version = resource['metadata']['resourceVersion']
                retrieved_annotations = resource['metadata'].get(
                    'annotations', {})

                for k, v in annotations.items():
                    if v != retrieved_annotations.get(k, v):
                        break
                else:
                    # No conflicting annotations found. Retry patching
                    resource_version = new_version
                    continue
                LOG.debug(
                    "Annotations for %(path)s already present: "
                    "%(names)s", {
                        'path': path,
                        'names': retrieved_annotations
                    })
            raise exc.K8sClientException(response.text)
 def test_update_security_group_rules_with_k8s_exc(self, m_parse, m_get_crd,
                                                   m_create_sgr):
     self._driver.kubernetes.patch_crd.side_effect = (
         exceptions.K8sClientException())
     m_get_crd.return_value = self._crd
     m_parse.return_value = (self._i_rules, self._e_rules)
     self.assertRaises(
         exceptions.K8sClientException,
         self._driver.update_security_group_rules_from_network_policy,
         self._policy)
     m_parse.assert_called_with(self._policy, self._sg_id)
Пример #6
0
 def _raise_from_response(self, response):
     if response.status_code == requests.codes.not_found:
         raise exc.K8sResourceNotFound(response.text)
     if response.status_code == requests.codes.conflict:
         raise exc.K8sConflict(response.text)
     if response.status_code == requests.codes.forbidden:
         if 'because it is being terminated' in response.json()['message']:
             raise exc.K8sNamespaceTerminating(response.text)
         raise exc.K8sForbidden(response.text)
     if not response.ok:
         raise exc.K8sClientException(response.text)
Пример #7
0
 def get(self, path):
     LOG.debug("Get %(path)s", {'path': path})
     url = self._base_url + path
     header = {}
     if self.token:
         header.update({'Authorization': 'Bearer %s' % self.token})
     response = requests.get(url, cert=self.cert,
                             verify=self.verify_server,
                             headers=header)
     if not response.ok:
         raise exc.K8sClientException(response.text)
     return response.json()
Пример #8
0
 def patch(self, field, path, data):
     LOG.debug("Patch %(path)s: %(data)s", {
         'path': path, 'data': data})
     if field == 'status':
         path = path + '/' + str(field)
     url, header = self._get_url_and_header(path)
     response = requests.patch(url, json={field: data},
                               headers=header, cert=self.cert,
                               verify=self.verify_server)
     if response.ok:
         return response.json().get('status')
     raise exc.K8sClientException(response.text)
Пример #9
0
    def post(self, path, body):
        LOG.debug("Post %(path)s: %(body)s", {'path': path, 'body': body})
        url = self._base_url + path
        header = {'Content-Type': 'application/json'}
        if self.token:
            header.update({'Authorization': 'Bearer %s' % self.token})

        response = self.session.post(url, json=body, cert=self.cert,
                                     verify=self.verify_server, headers=header)
        if response.ok:
            return response.json()
        raise exc.K8sClientException(response)
Пример #10
0
    def delete(self, path):
        LOG.debug("Delete %(path)s", {'path': path})
        url = self._base_url + path
        header = {'Content-Type': 'application/json'}
        if self.token:
            header.update({'Authorization': 'Bearer %s' % self.token})

        response = requests.delete(url, cert=self.cert,
                                   verify=self.verify_server, headers=header)
        if response.ok:
            return response.json()
        raise exc.K8sClientException(response)
Пример #11
0
    def watch(self, path):
        url = self._base_url + path
        resource_version = None
        header = {}
        timeouts = (CONF.kubernetes.watch_connection_timeout,
                    CONF.kubernetes.watch_read_timeout)
        if self.token:
            header.update({'Authorization': 'Bearer %s' % self.token})

        while True:
            try:
                params = {'watch': 'true'}
                if resource_version:
                    params['resourceVersion'] = resource_version
                with contextlib.closing(
                        self.session.get(url,
                                         params=params,
                                         stream=True,
                                         cert=self.cert,
                                         verify=self.verify_server,
                                         headers=header,
                                         timeout=timeouts)) as response:
                    if not response.ok:
                        raise exc.K8sClientException(response.text)
                    for line in response.iter_lines():
                        line = line.decode('utf-8').strip()
                        if line:
                            line_dict = jsonutils.loads(line)
                            yield line_dict
                            # Saving the resourceVersion in case of a restart.
                            # At this point it's safely passed to handler.
                            m = line_dict.get('object', {}).get('metadata', {})
                            resource_version = m.get('resourceVersion', None)
            except (requests.ReadTimeout, requests.ConnectionError,
                    ssl.SSLError) as e:
                if isinstance(e, ssl.SSLError) and e.args != ('timed out', ):
                    raise

                LOG.warning(
                    '%ds without data received from watching %s. '
                    'Retrying the connection with resourceVersion=%s.',
                    timeouts[1], path, params.get('resourceVersion'))
            except requests.exceptions.ChunkedEncodingError:
                LOG.warning(
                    "Connection to %s closed when watching. This "
                    "mostly happens when Octavia's Amphora closes "
                    "connection due to lack of activity for 50s. "
                    "Since Rocky Octavia this is configurable and "
                    "should be set to at least 20m, so check timeouts "
                    "on Kubernetes API LB listener. Restarting "
                    "connection with resourceVersion=%s.", path,
                    params.get('resourceVersion'))
Пример #12
0
 def patch(self, field, path, data):
     LOG.debug("Patch %(path)s: %(data)s", {
         'path': path, 'data': data})
     if field == 'status':
         path = path + '/' + str(field)
     content_type = 'application/merge-patch+json'
     url, header = self._get_url_and_header(path, content_type)
     response = self.session.patch(url, json={field: data},
                                   headers=header, cert=self.cert,
                                   verify=self.verify_server)
     if response.ok:
         return response.json().get('status')
     raise exc.K8sClientException(response.text)
Пример #13
0
    def remove_node_annotations(self, node, annotation_name):
        content_type = 'application/json-patch+json'
        path = '{}/nodes/{}/'.format(constants.K8S_API_BASE, node)
        url, header = self._get_url_and_header(path, content_type)

        data = [{'op': 'remove',
                 'path': '/metadata/annotations/{}'.format(annotation_name)}]

        response = self.session.patch(url, data=jsonutils.dumps(data),
                                      headers=header, cert=self.cert,
                                      verify=self.verify_server)
        if response.ok:
            return response.json().get('status')
        raise exc.K8sClientException(response.text)
Пример #14
0
    def remove_annotations(self, path, annotation_name):
        LOG.debug("Remove annotations %(path)s: %(name)s",
                  {'path': path, 'name': annotation_name})
        content_type = 'application/json-patch+json'
        url, header = self._get_url_and_header(path, content_type)
        annotation_name = self._jsonpatch_escape(annotation_name)

        data = [{'op': 'remove',
                 'path': f'/metadata/annotations/{annotation_name}'}]
        response = self.session.patch(url, data=jsonutils.dumps(data),
                                      headers=header)
        if response.ok:
            return response.json().get('status')
        raise exc.K8sClientException(response.text)
Пример #15
0
    def watch(self, path):
        params = {'watch': 'true'}
        url = self._base_url + path

        # TODO(ivc): handle connection errors and retry on failure
        while True:
            with contextlib.closing(
                    requests.get(url, params=params, stream=True)) as response:
                if not response.ok:
                    raise exc.K8sClientException(response.text)
                for line in response.iter_lines(delimiter='\n'):
                    line = line.strip()
                    if line:
                        yield jsonutils.loads(line)
Пример #16
0
    def watch(self, path):
        url = self._base_url + path
        resource_version = None
        header = {}
        timeouts = (CONF.kubernetes.watch_connection_timeout,
                    CONF.kubernetes.watch_read_timeout)
        if self.token:
            header.update({'Authorization': 'Bearer %s' % self.token})

        attempt = 0
        while True:
            try:
                params = {'watch': 'true'}
                if resource_version:
                    params['resourceVersion'] = resource_version
                with contextlib.closing(
                        self.session.get(url,
                                         params=params,
                                         stream=True,
                                         cert=self.cert,
                                         verify=self.verify_server,
                                         headers=header,
                                         timeout=timeouts)) as response:
                    if not response.ok:
                        raise exc.K8sClientException(response.text)
                    attempt = 0
                    for line in response.iter_lines():
                        line = line.decode('utf-8').strip()
                        if line:
                            line_dict = jsonutils.loads(line)
                            yield line_dict
                            # Saving the resourceVersion in case of a restart.
                            # At this point it's safely passed to handler.
                            m = line_dict.get('object', {}).get('metadata', {})
                            resource_version = m.get('resourceVersion', None)
            except (requests.ReadTimeout, requests.ConnectionError,
                    ssl.SSLError, requests.exceptions.ChunkedEncodingError):
                t = utils.exponential_backoff(attempt)
                log = LOG.debug
                if attempt > 0:
                    # Only make it a warning if it's happening again, no need
                    # to inform about all the read timeouts.
                    log = LOG.warning
                log(
                    'Connection error when watching %s. Retrying in %ds with '
                    'resourceVersion=%s', path, t,
                    params.get('resourceVersion'))
                time.sleep(t)
                attempt += 1
Пример #17
0
    def delete(self, path):
        LOG.debug("Delete %(path)s", {'path': path})
        url = self._base_url + path
        header = {'Content-Type': 'application/json'}
        if self.token:
            header.update({'Authorization': 'Bearer %s' % self.token})

        response = self.session.delete(url, cert=self.cert,
                                       verify=self.verify_server,
                                       headers=header)
        if response.ok:
            return response.json()
        else:
            if response.status_code == requests.codes.not_found:
                raise exc.K8sResourceNotFound(response.text)
            raise exc.K8sClientException(response)
Пример #18
0
 def get(self, path, json=True, headers=None):
     LOG.debug("Get %(path)s", {'path': path})
     url = self._base_url + path
     header = {}
     if self.token:
         header.update({'Authorization': 'Bearer %s' % self.token})
     if headers:
         header.update(headers)
     response = self.session.get(url, cert=self.cert,
                                 verify=self.verify_server,
                                 headers=header)
     if response.status_code == requests.codes.not_found:
         raise exc.K8sResourceNotFound(response.text)
     if not response.ok:
         raise exc.K8sClientException(response.text)
     result = response.json() if json else response.text
     return result
Пример #19
0
    def patch_crd(self, field, path, data):
        content_type = 'application/json-patch+json'
        url, header = self._get_url_and_header(path, content_type)

        data = [{'op': 'replace',
                 'path': '/{}/{}'.format(field, np_field),
                 'value': value}
                for np_field, value in data.items()]

        LOG.debug("Patch %(path)s: %(data)s", {
            'path': path, 'data': data})

        response = self.session.patch(url, data=jsonutils.dumps(data),
                                      headers=header, cert=self.cert,
                                      verify=self.verify_server)
        if response.ok:
            return response.json().get('status')
        raise exc.K8sClientException(response.text)
Пример #20
0
    def remove_annotations(self, path, annotation_name):
        content_type = 'application/json-patch+json'
        url, header = self._get_url_and_header(path, content_type)

        data = [{
            'op': 'remove',
            'path': '/metadata/annotations',
            'value': annotation_name
        }]

        response = self.session.patch(url,
                                      data=jsonutils.dumps(data),
                                      headers=header,
                                      cert=self.cert,
                                      verify=self.verify_server)
        if response.ok:
            return response.json().get('status')
        raise exc.K8sClientException(response.text)
Пример #21
0
    def watch(self, path):
        params = {'watch': 'true'}
        url = self._base_url + path
        header = {}
        if self.token:
            header.update({'Authorization': 'Bearer %s' % self.token})

        # TODO(ivc): handle connection errors and retry on failure
        while True:
            with contextlib.closing(
                    requests.get(url, params=params, stream=True,
                                 cert=self.cert, verify=self.verify_server,
                                 headers=header)) as response:
                if not response.ok:
                    raise exc.K8sClientException(response.text)
                for line in response.iter_lines(delimiter='\n'):
                    line = line.strip()
                    if line:
                        yield jsonutils.loads(line)
Пример #22
0
 def _raise_from_response(self, response):
     if response.status_code == requests.codes.not_found:
         raise exc.K8sResourceNotFound(response.text)
     if response.status_code == requests.codes.conflict:
         raise exc.K8sConflict(response.text)
     if response.status_code == requests.codes.forbidden:
         if 'because it is being terminated' in response.json()['message']:
             raise exc.K8sNamespaceTerminating(response.text)
         raise exc.K8sForbidden(response.text)
     if response.status_code == requests.codes.unprocessable_entity:
         # NOTE(gryf): on k8s API code 422 is also Forbidden, but specified
         # to FieldValueForbidden. Perhaps there are other usages for
         # throwing unprocessable entity errors in different cases.
         if ('FieldValueForbidden' in response.text
                 and 'Forbidden' in response.json()['message']):
             raise exc.K8sFieldValueForbidden(response.text)
         raise exc.K8sUnprocessableEntity(response.text)
     if not response.ok:
         raise exc.K8sClientException(response.text)
Пример #23
0
    def test_on_present_exception_during_update_crd(self, ged, get_k8s_client,
                                                    activate_vif, update_crd,
                                                    get_project, get_sg,
                                                    release_vif):
        ged.return_value = [self._driver]
        kp = kuryrport.KuryrPortHandler()
        self._kp['status']['vifs'] = self._vifs_primitive
        update_crd.side_effect = k_exc.K8sClientException()
        get_project.return_value = self._project_id
        get_sg.return_value = self._security_groups

        with mock.patch.object(kp, 'k8s') as k8s:
            k8s.get.return_value = self._pod

            self.assertRaises(k_exc.ResourceNotReady, kp.on_present, self._kp)

            k8s.get.assert_called_once_with(self._pod_uri)

        update_crd.assert_called_once_with(self._kp, self._vifs)
Пример #24
0
 def annotate(self, path, annotations, resource_version=None):
     url = self._base_url + path
     data = jsonutils.dumps(
         {
             "metadata": {
                 "annotations": annotations,
                 "resourceVersion": resource_version,
             }
         },
         sort_keys=True)
     response = requests.patch(url,
                               data=data,
                               headers={
                                   'Content-Type':
                                   'application/merge-patch+json',
                                   'Accept': 'application/json',
                               })
     if not response.ok:
         raise exc.K8sClientException(response.text)
     return response.json()['metadata']['annotations']
Пример #25
0
 def get(self, path):
     url = self._base_url + path
     response = requests.get(url)
     if not response.ok:
         raise exc.K8sClientException(response.text)
     return response.json()
Пример #26
0
 def test__remove_endpoints_client_exception(self):
     m_handler = mock.Mock()
     m_handler.k8s.patch_crd.side_effect = k_exc.K8sClientException()
     self.assertRaises(k_exc.K8sClientException,
                       h_lbaas.EndpointsHandler._remove_endpoints,
                       m_handler, self._ep)
Пример #27
0
 def _raise_from_response(self, response):
     if response.status_code == requests.codes.not_found:
         raise exc.K8sResourceNotFound(response.text)
     if not response.ok:
         raise exc.K8sClientException(response.text)