func (tw *baseTimeoutWriter) timeout(msg string) { tw.mu.Lock() defer tw.mu.Unlock() tw.timedOut = true // The timeout writer has not been used by the inner handler. // We can safely timeout the HTTP request by sending by a timeout // handler if !tw.wroteHeader && !tw.hijacked { tw.w.WriteHeader(http.StatusGatewayTimeout) if msg != "" { tw.w.Write([]byte(msg)) } else { enc := json.NewEncoder(tw.w) enc.Encode(errors.NewServerTimeout(api.Resource(""), "", 0)) } } else { // The timeout writer has been used by the inner handler. There is // no way to timeout the HTTP request at the point. We have to shutdown // the connection for HTTP1 or reset stream for HTTP2. // // Note from: Brad Fitzpatrick // if the ServeHTTP goroutine panics, that will do the best possible thing for both // HTTP/1 and HTTP/2. In HTTP/1, assuming you're replying with at least HTTP/1.1 and // you've already flushed the headers so it's using HTTP chunking, it'll kill the TCP // connection immediately without a proper 0-byte EOF chunk, so the peer will recognize // the response as bogus. In HTTP/2 the server will just RST_STREAM the stream, leaving // the TCP connection open, but resetting the stream to the peer so it'll have an error, // like the HTTP/1 case. panic(errConnKilled) } }
// InterpretDeleteError converts a generic error on a delete // operation into the appropriate API error. func InterpretDeleteError(err error, qualifiedResource unversioned.GroupResource, name string) error { switch { case storage.IsNotFound(err): return errors.NewNotFound(qualifiedResource, name) case storage.IsUnreachable(err): return errors.NewServerTimeout(qualifiedResource, "delete", 2) // TODO: make configurable or handled at a higher level default: return err } }
// InterpretUpdateError converts a generic etcd error on a update // operation into the appropriate API error. func InterpretUpdateError(err error, kind, name string) error { switch { case etcdutil.IsEtcdTestFailed(err), etcdutil.IsEtcdNodeExist(err): return errors.NewConflict(kind, name, err) case etcdutil.IsEtcdUnreachable(err): return errors.NewServerTimeout(kind, "update", 2) // TODO: make configurable or handled at a higher level default: return err } }
// InterpretDeleteError converts a generic etcd error on a delete // operation into the appropriate API error. func InterpretDeleteError(err error, kind, name string) error { switch { case etcdutil.IsEtcdNotFound(err): return errors.NewNotFound(kind, name) case etcdutil.IsEtcdUnreachable(err): return errors.NewServerTimeout(kind, "delete", 2) // TODO: make configurable or handled at a higher level default: return err } }
// InterpretGetError converts a generic error on a retrieval // operation into the appropriate API error. func InterpretGetError(err error, kind, name string) error { switch { case storage.IsNotFound(err): return errors.NewNotFound(kind, name) case storage.IsUnreachable(err): return errors.NewServerTimeout(kind, "get", 2) // TODO: make configurable or handled at a higher level default: return err } }
// InterpretCreateError converts a generic etcd error on a create // operation into the appropriate API error. func InterpretCreateError(err error, kind, name string) error { switch { case etcdutil.IsEtcdNodeExist(err): return errors.NewAlreadyExists(kind, name) case etcdutil.IsEtcdUnreachable(err): return errors.NewServerTimeout(kind, "create", 2) // TODO: make configurable or handled at a higher level default: return err } }
// InterpretCreateError converts a generic error on a create // operation into the appropriate API error. func InterpretCreateError(err error, qualifiedResource schema.GroupResource, name string) error { switch { case storage.IsNodeExist(err): return errors.NewAlreadyExists(qualifiedResource, name) case storage.IsUnreachable(err): return errors.NewServerTimeout(qualifiedResource, "create", 2) // TODO: make configurable or handled at a higher level default: return err } }
// InterpretListError converts a generic error on a retrieval // operation into the appropriate API error. func InterpretListError(err error, qualifiedResource schema.GroupResource) error { switch { case storage.IsNotFound(err): return errors.NewNotFound(qualifiedResource, "") case storage.IsUnreachable(err): return errors.NewServerTimeout(qualifiedResource, "list", 2) // TODO: make configurable or handled at a higher level default: return err } }
// InterpretListError converts a generic etcd error on a retrieval // operation into the appropriate API error. func InterpretListError(err error, kind string) error { switch { case etcdstorage.IsEtcdNotFound(err): return errors.NewNotFound(kind, "") case etcdstorage.IsEtcdUnreachable(err): return errors.NewServerTimeout(kind, "list", 2) // TODO: make configurable or handled at a higher level default: return err } }
// InterpretUpdateError converts a generic error on a update // operation into the appropriate API error. func InterpretUpdateError(err error, qualifiedResource unversioned.GroupResource, name string) error { switch { case storage.IsTestFailed(err), storage.IsNodeExist(err): return errors.NewConflict(qualifiedResource, name, err) case storage.IsUnreachable(err): return errors.NewServerTimeout(qualifiedResource, "update", 2) // TODO: make configurable or handled at a higher level default: return err } }
func (tw *baseTimeoutWriter) timeout(msg string) { tw.mu.Lock() defer tw.mu.Unlock() if !tw.wroteHeader && !tw.hijacked { tw.w.WriteHeader(http.StatusGatewayTimeout) if msg != "" { tw.w.Write([]byte(msg)) } else { enc := json.NewEncoder(tw.w) enc.Encode(errors.NewServerTimeout(api.Resource(""), "", 0)) } } tw.timedOut = true }
// InterpretDeleteError converts a generic error on a delete // operation into the appropriate API error. func InterpretDeleteError(err error, qualifiedResource schema.GroupResource, name string) error { switch { case storage.IsNotFound(err): return errors.NewNotFound(qualifiedResource, name) case storage.IsUnreachable(err): return errors.NewServerTimeout(qualifiedResource, "delete", 2) // TODO: make configurable or handled at a higher level case storage.IsTestFailed(err), storage.IsNodeExist(err), storage.IsInvalidObj(err): return errors.NewConflict(qualifiedResource, name, err) case storage.IsInternalError(err): return errors.NewInternalError(err) default: return err } }
// CheckGeneratedNameError checks whether an error that occurred creating a resource is due // to generation being unable to pick a valid name. func CheckGeneratedNameError(strategy RESTCreateStrategy, err error, obj runtime.Object) error { if !errors.IsAlreadyExists(err) { return err } objectMeta, kind, kerr := objectMetaAndKind(strategy, obj) if kerr != nil { return kerr } if len(objectMeta.GenerateName) == 0 { return err } return errors.NewServerTimeout(kind, "POST", 0) }
func (s *serviceAccount) mountServiceAccountToken(serviceAccount *api.ServiceAccount, pod *api.Pod) error { // Find the name of a referenced ServiceAccountToken secret we can mount serviceAccountToken, err := s.getReferencedServiceAccountToken(serviceAccount) if err != nil { return fmt.Errorf("Error looking up service account token for %s/%s: %v", serviceAccount.Namespace, serviceAccount.Name, err) } if len(serviceAccountToken) == 0 { // We don't have an API token to mount, so return if s.RequireAPIToken { // If a token is required, this is considered an error err := errors.NewServerTimeout(unversioned.GroupResource{Resource: "serviceaccounts"}, "create pod", 1) err.ErrStatus.Message = fmt.Sprintf("No API token found for service account %q, retry after the token is automatically created and added to the service account", serviceAccount.Name) return err } return nil } // Find the volume and volume name for the ServiceAccountTokenSecret if it already exists tokenVolumeName := "" hasTokenVolume := false allVolumeNames := sets.NewString() for _, volume := range pod.Spec.Volumes { allVolumeNames.Insert(volume.Name) if volume.Secret != nil && volume.Secret.SecretName == serviceAccountToken { tokenVolumeName = volume.Name hasTokenVolume = true break } } // Determine a volume name for the ServiceAccountTokenSecret in case we need it if len(tokenVolumeName) == 0 { // Try naming the volume the same as the serviceAccountToken, and uniquify if needed tokenVolumeName = serviceAccountToken if allVolumeNames.Has(tokenVolumeName) { tokenVolumeName = api.SimpleNameGenerator.GenerateName(fmt.Sprintf("%s-", serviceAccountToken)) } } // Create the prototypical VolumeMount volumeMount := api.VolumeMount{ Name: tokenVolumeName, ReadOnly: true, MountPath: DefaultAPITokenMountPath, } // Ensure every container mounts the APISecret volume needsTokenVolume := false for i, container := range pod.Spec.InitContainers { existingContainerMount := false for _, volumeMount := range container.VolumeMounts { // Existing mounts at the default mount path prevent mounting of the API token if volumeMount.MountPath == DefaultAPITokenMountPath { existingContainerMount = true break } } if !existingContainerMount { pod.Spec.InitContainers[i].VolumeMounts = append(pod.Spec.InitContainers[i].VolumeMounts, volumeMount) needsTokenVolume = true } } for i, container := range pod.Spec.Containers { existingContainerMount := false for _, volumeMount := range container.VolumeMounts { // Existing mounts at the default mount path prevent mounting of the API token if volumeMount.MountPath == DefaultAPITokenMountPath { existingContainerMount = true break } } if !existingContainerMount { pod.Spec.Containers[i].VolumeMounts = append(pod.Spec.Containers[i].VolumeMounts, volumeMount) needsTokenVolume = true } } // Add the volume if a container needs it if !hasTokenVolume && needsTokenVolume { volume := api.Volume{ Name: tokenVolumeName, VolumeSource: api.VolumeSource{ Secret: &api.SecretVolumeSource{ SecretName: serviceAccountToken, }, }, } pod.Spec.Volumes = append(pod.Spec.Volumes, volume) } return nil }
// Get returns a streamer resource with the contents of the deployment log func (r *REST) Get(ctx kapi.Context, name string, opts runtime.Object) (runtime.Object, error) { // Ensure we have a namespace in the context namespace, ok := kapi.NamespaceFrom(ctx) if !ok { return nil, errors.NewBadRequest("namespace parameter required.") } // Validate DeploymentLogOptions deployLogOpts, ok := opts.(*deployapi.DeploymentLogOptions) if !ok { return nil, errors.NewBadRequest("did not get an expected options.") } if errs := validation.ValidateDeploymentLogOptions(deployLogOpts); len(errs) > 0 { return nil, errors.NewInvalid(deployapi.Kind("DeploymentLogOptions"), "", errs) } // Fetch deploymentConfig and check latest version; if 0, there are no deployments // for this config config, err := r.dn.DeploymentConfigs(namespace).Get(name) if err != nil { return nil, errors.NewNotFound(deployapi.Resource("deploymentconfig"), name) } desiredVersion := config.Status.LatestVersion if desiredVersion == 0 { return nil, errors.NewBadRequest(fmt.Sprintf("no deployment exists for deploymentConfig %q", config.Name)) } // Support retrieving logs for older deployments switch { case deployLogOpts.Version == nil: // Latest or previous if deployLogOpts.Previous { desiredVersion-- if desiredVersion < 1 { return nil, errors.NewBadRequest(fmt.Sprintf("no previous deployment exists for deploymentConfig %q", config.Name)) } } case *deployLogOpts.Version <= 0 || *deployLogOpts.Version > config.Status.LatestVersion: // Invalid version return nil, errors.NewBadRequest(fmt.Sprintf("invalid version for deploymentConfig %q: %d", config.Name, *deployLogOpts.Version)) default: desiredVersion = *deployLogOpts.Version } // Get desired deployment targetName := deployutil.DeploymentNameForConfigVersion(config.Name, desiredVersion) target, err := r.waitForExistingDeployment(namespace, targetName) if err != nil { return nil, err } podName := deployutil.DeployerPodNameForDeployment(target.Name) // Check for deployment status; if it is new or pending, we will wait for it. If it is complete, // the deployment completed successfully and the deployer pod will be deleted so we will return a // success message. If it is running or failed, retrieve the log from the deployer pod. status := deployutil.DeploymentStatusFor(target) switch status { case deployapi.DeploymentStatusNew, deployapi.DeploymentStatusPending: if deployLogOpts.NoWait { glog.V(4).Infof("Deployment %s is in %s state. No logs to retrieve yet.", deployutil.LabelForDeployment(target), status) return &genericrest.LocationStreamer{}, nil } glog.V(4).Infof("Deployment %s is in %s state, waiting for it to start...", deployutil.LabelForDeployment(target), status) if err := deployutil.WaitForRunningDeployerPod(r.pn, target, r.timeout); err != nil { return nil, errors.NewBadRequest(fmt.Sprintf("failed to run deployer pod %s: %v", podName, err)) } latest, ok, err := registry.WaitForRunningDeployment(r.rn, target, r.timeout) if err != nil { return nil, errors.NewBadRequest(fmt.Sprintf("unable to wait for deployment %s to run: %v", deployutil.LabelForDeployment(target), err)) } if !ok { return nil, errors.NewServerTimeout(kapi.Resource("ReplicationController"), "get", 2) } if deployutil.DeploymentStatusFor(latest) == deployapi.DeploymentStatusComplete { podName, err = r.returnApplicationPodName(target) if err != nil { return nil, err } } case deployapi.DeploymentStatusComplete: podName, err = r.returnApplicationPodName(target) if err != nil { return nil, err } } logOpts := deployapi.DeploymentToPodLogOptions(deployLogOpts) location, transport, err := pod.LogLocation(&podGetter{r.pn}, r.connInfo, ctx, podName, logOpts) if err != nil { return nil, errors.NewBadRequest(err.Error()) } return &genericrest.LocationStreamer{ Location: location, Transport: transport, ContentType: "text/plain", Flush: deployLogOpts.Follow, ResponseChecker: genericrest.NewGenericHttpResponseChecker(kapi.Resource("pod"), podName), }, nil }