Skip to content

Commit

Permalink
imagesprune: Determina protocol just once
Browse files Browse the repository at this point in the history
Determine the registry protocol once. Do not change to other protocol
during the run. This will produce nicer output without unrelated
protocol fallback errors.

Signed-off-by: Michal Minář <[email protected]>
  • Loading branch information
Michal Minář committed Jun 27, 2017
1 parent 247631a commit c3d693f
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 76 deletions.
177 changes: 103 additions & 74 deletions pkg/image/prune/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"reflect"
"sort"
"strings"
Expand Down Expand Up @@ -171,7 +172,7 @@ var _ Pruner = &pruner{}
// registryPinger performs a health check against a registry.
type registryPinger interface {
// ping performs a health check against registry.
ping(registry string) error
ping(registry string) (string, error)
}

// defaultRegistryPinger implements registryPinger.
Expand All @@ -180,10 +181,11 @@ type defaultRegistryPinger struct {
insecure bool
}

func (drp *defaultRegistryPinger) ping(registry string) error {
healthCheck := func(proto, registry string) error {
func (drp *defaultRegistryPinger) ping(registry string) (string, error) {
registryURL, err := tryProtocolsWithRegistryURL(registry, drp.insecure, func(u url.URL) error {
// TODO: `/healthz` route is deprecated by `/`; remove it in future versions
healthResponse, err := drp.client.Get(fmt.Sprintf("%s://%s/healthz", proto, registry))
u.Path = "healthz"
healthResponse, err := drp.client.Get(u.String())
if err != nil {
return err
}
Expand All @@ -194,33 +196,21 @@ func (drp *defaultRegistryPinger) ping(registry string) error {
}

return nil
}
})

var errs []error
protos := make([]string, 0, 2)
protos = append(protos, "https")
if drp.insecure || netutils.IsPrivateAddress(registry) {
protos = append(protos, "http")
}
for _, proto := range protos {
glog.V(4).Infof("Trying %s for %s", proto, registry)
err := healthCheck(proto, registry)
if err == nil {
return nil
}
errs = append(errs, err)
glog.V(4).Infof("Error with %s for %s: %v", proto, registry, err)
if err != nil {
return registry, err
}

return kerrors.NewAggregate(errs)
return registryURL.String(), nil
}

// dryRunRegistryPinger implements registryPinger.
type dryRunRegistryPinger struct {
}

func (*dryRunRegistryPinger) ping(registry string) error {
return nil
func (*dryRunRegistryPinger) ping(registry string) (string, error) {
return "http", nil
}

// NewPruner creates a Pruner.
Expand Down Expand Up @@ -896,14 +886,15 @@ func (p *pruner) Prune(
return nil
}

registryURL, err := p.determineRegistry(imageNodes, getImageStreamNodes(allNodes))
registryAddr, err := p.determineRegistry(imageNodes, getImageStreamNodes(allNodes))
if err != nil {
return fmt.Errorf("unable to determine registry: %v", err)
}
glog.V(1).Infof("Using registry: %s", registryURL)
glog.V(1).Infof("Using registry: %s", registryAddr)

if err := p.registryPinger.ping(registryURL); err != nil {
return fmt.Errorf("error communicating with registry %s: %v", registryURL, err)
registryURL, err := p.registryPinger.ping(registryAddr)
if err != nil {
return fmt.Errorf("error communicating with registry %s: %v", registryAddr, err)
}

prunableImageNodes, prunableImageIDs := calculatePrunableImages(p.g, imageNodes)
Expand Down Expand Up @@ -1077,63 +1068,42 @@ func (p *imageStreamDeleter) DeleteImageStream(stream *imageapi.ImageStream, ima
// provided url. It attempts an https request first; if that fails, it fails
// back to http.
func deleteFromRegistry(registryClient *http.Client, url string) error {
deleteFunc := func(proto, url string) error {
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}

glog.V(4).Infof("Sending request to registry")
resp, err := registryClient.Do(req)
if err != nil {
if proto != "https" && strings.Contains(err.Error(), "malformed HTTP response") {
return fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled registry without TLS?", err)
}
return err
}
defer resp.Body.Close()
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}

// TODO: investigate why we're getting non-existent layers, for now we're logging
// them out and continue working
if resp.StatusCode == http.StatusNotFound {
glog.Warningf("Unable to prune layer %s, returned %v", url, resp.Status)
return nil
}
// non-2xx/3xx response doesn't cause an error, so we need to check for it
// manually and return it to caller
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest {
return fmt.Errorf(resp.Status)
}
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusAccepted {
glog.V(1).Infof("Unexpected status code in response: %d", resp.StatusCode)
var response errcode.Errors
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&response); err != nil {
return err
}
glog.V(1).Infof("Response: %#v", response)
return &response
}
glog.V(4).Infof("Sending request to registry")
resp, err := registryClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

// TODO: investigate why we're getting non-existent layers, for now we're logging
// them out and continue working
if resp.StatusCode == http.StatusNotFound {
glog.Warningf("Unable to prune layer %s, returned %v", url, resp.Status)
return nil
}

var err error
for _, proto := range []string{"https", "http"} {
glog.V(4).Infof("Trying %s for %s", proto, url)
err = deleteFunc(proto, fmt.Sprintf("%s://%s", proto, url))
if err == nil {
return nil
}
// non-2xx/3xx response doesn't cause an error, so we need to check for it
// manually and return it to caller
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest {
return fmt.Errorf(resp.Status)
}

if _, ok := err.(*errcode.Errors); ok {
// we got a response back from the registry, so return it
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusAccepted {
glog.V(1).Infof("Unexpected status code in response: %d", resp.StatusCode)
var response errcode.Errors
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&response); err != nil {
return err
}

// we didn't get a success or a errcode.Errors response back from the registry
glog.V(4).Infof("Error with %s for %s: %v", proto, url, err)
glog.V(1).Infof("Response: %#v", response)
return &response
}

return err
}

Expand Down Expand Up @@ -1190,3 +1160,62 @@ func getName(obj runtime.Object) string {
}
return fmt.Sprintf("%s/%s", accessor.GetNamespace(), accessor.GetName())
}

// tryProtocolsWithRegistryURL runs given callback with different protocols until no error is returned. The
// https protocol is the first attempt. If it fails and allowInsecure is true, http will be the next. Obtained
// errors will be concatenated and returned.
func tryProtocolsWithRegistryURL(registry string, allowInsecure bool, callback func(registryURL url.URL) error) (*url.URL, error) {
var errs []error

if !strings.Contains(registry, "://") {
registry = "unset://" + registry
}
url, err := url.Parse(registry)
if err != nil {
return nil, err
}
var protos []string
switch {
case len(url.Scheme) > 0 && url.Scheme != "unset":
protos = []string{url.Scheme}
case allowInsecure || netutils.IsPrivateAddress(registry):
protos = []string{"https", "http"}
default:
protos = []string{"https"}
}
registry = url.Host
glog.V(4).Infof("url: %#+v", url)

for i, proto := range protos {
switch i {
case 0:
glog.V(4).Infof("Trying protocol %s for the registry url %s", proto, registry)
default:
glog.V(4).Infof("Falling back to protocol %s for the registry url %s", proto, registry)
}
url.Scheme = proto
err := callback(*url)
if err == nil {
return url, nil
}

if err != nil {
// TODO: remove the first
glog.V(1).Infof("Error with %s for %s: %#+v", proto, registry, err)
glog.V(4).Infof("Error with %s for %s: %v", proto, registry, err)
}

if _, ok := err.(*errcode.Errors); ok {
// we got a response back from the registry, so return it
return url, err
}
errs = append(errs, err)
if proto == "https" && strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") && !allowInsecure {
errs = append(errs, fmt.Errorf("\n* Append --force-insecure if you really want to prune the registry using insecure connection."))
} else if proto == "http" && strings.Contains(err.Error(), "malformed HTTP response") {
errs = append(errs, fmt.Errorf("\n* Are you trying to connect to a TLS-enabled registry without TLS?"))
}
}

return nil, kerrors.NewAggregate(errs)
}
7 changes: 5 additions & 2 deletions pkg/image/prune/prune_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ type fakeRegistryPinger struct {
requests []string
}

func (f *fakeRegistryPinger) ping(registry string) error {
func (f *fakeRegistryPinger) ping(registry string) (string, error) {
f.requests = append(f.requests, registry)
return f.err
if f.err == nil {
return "http", nil
}
return "", f.err
}

func imageList(images ...imageapi.Image) imageapi.ImageList {
Expand Down

0 comments on commit c3d693f

Please sign in to comment.