<commit_before>/*
Copyright 2020 The Knative Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// rollout.go contains the types and functions to deal with
// gradual rollout of the new revision for a configuration target.
// The types in this file are expected to be serialized as strings
// and used as annotations for progressive rollout logic.

package traffic

import (
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestRoll(t *testing.T) {
	tests := []struct {
		name            string
		prev, cur, want *Rollout
	}{{
		name: "no prev",
		cur:  &Rollout{},
		prev: nil,
		want: &Rollout{},
	}, {
		name: "simplest, same",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "let-it-bleed",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "let-it-bleed",
					Percent:      100,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "let-it-bleed",
					Percent:      100,
				}},
			}},
		},
	}, {
		name: "simplest, roll",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "let-it-bleed",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      100,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      99,
				}, {
					RevisionName: "let-it-bleed",
					Percent:      1,
				}},
			}},
		},
	}, {
		name: "roll with two existing, no deletes",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "sticky-fingers",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      95,
				}, {
					RevisionName: "beggars-banquet",
					Percent:      5, // 5 should become 4.
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      95,
				}, {
					RevisionName: "beggars-banquet",
					Percent:      4,
				}, {
					RevisionName: "sticky-fingers",
					Percent:      1,
				}},
			}},
		},
	}, {
		name: "roll with delete (two fast successive rolls)",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "between-the-buttons",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      99,
				}, {
					RevisionName: "bridges-to-babylon",
					Percent:      1,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      99,
				}, {
					RevisionName: "between-the-buttons",
					Percent:      1,
				}},
			}},
		},
	}, {
		name: "new tag, no roll", // just attached a tag to an existing route.
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}, {
				ConfigurationName: "mick",
				Tag:               "jagger",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}, {
				ConfigurationName: "mick",
				Tag:               "jagger",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}},
		},
	}, {
		name: "deleted config, no roll",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      50,
				}},
			}, {
				ConfigurationName: "mick",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      50,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      100,
				}},
			}},
		},
	}, {
		name: "new config, no roll, newer smaller",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      50,
				}},
			}, {
				ConfigurationName: "mick",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      50,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      100,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      50,
				}},
			}, {
				ConfigurationName: "mick",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      50,
				}},
			}},
		},
	}, {
		name: "new config, no roll, newer larger",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      50,
				}},
			}, {
				ConfigurationName: "mick",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      50,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      50,
				}},
			}, {
				ConfigurationName: "mick",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      50,
				}},
			}},
		},
	}}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got, want := tc.cur.Step(tc.prev), tc.want; !cmp.Equal(got, want) {
				t.Errorf("Wrong rolled rollout, diff(-want,+got):\n%s", cmp.Diff(want, got))
			}
		})
	}
}
<commit_msg>Add a few engecases for the traffic rollout (#10047)<commit_after>/*
Copyright 2020 The Knative Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// rollout.go contains the types and functions to deal with
// gradual rollout of the new revision for a configuration target.
// The types in this file are expected to be serialized as strings
// and used as annotations for progressive rollout logic.

package traffic

import (
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestRoll(t *testing.T) {
	tests := []struct {
		name            string
		prev, cur, want *Rollout
	}{{
		name: "no prev",
		cur:  &Rollout{},
		prev: nil,
		want: &Rollout{},
	}, {
		name: "simplest, same",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "let-it-bleed",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "let-it-bleed",
					Percent:      100,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "let-it-bleed",
					Percent:      100,
				}},
			}},
		},
	}, {
		name: "simplest, roll",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "let-it-bleed",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      100,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      99,
				}, {
					RevisionName: "let-it-bleed",
					Percent:      1,
				}},
			}},
		},
	}, {
		name: "roll, where sum < 100% (one route targets a revision, e.g.)",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "brian",
				Percent:           70,
				Revisions: []RevisionRollout{{
					RevisionName: "let-it-bleed",
					Percent:      70,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "brian",
				Percent:           70,
				Revisions: []RevisionRollout{{
					RevisionName: "exile-on-main-st",
					Percent:      70,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "brian",
				Percent:           70,
				Revisions: []RevisionRollout{{
					RevisionName: "exile-on-main-st",
					Percent:      69,
				}, {
					RevisionName: "let-it-bleed",
					Percent:      1,
				}},
			}},
		},
	}, {
		name: "roll with two existing, no deletes",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "sticky-fingers",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      95,
				}, {
					RevisionName: "beggars-banquet",
					Percent:      5, // 5 should become 4.
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      95,
				}, {
					RevisionName: "beggars-banquet",
					Percent:      4,
				}, {
					RevisionName: "sticky-fingers",
					Percent:      1,
				}},
			}},
		},
	}, {
		name: "roll with delete (two fast successive rolls)",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "between-the-buttons",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      99,
				}, {
					RevisionName: "bridges-to-babylon",
					Percent:      1,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "goat-head-soup",
					Percent:      99,
				}, {
					RevisionName: "between-the-buttons",
					Percent:      1,
				}},
			}},
		},
	}, {
		name: "roll with delete (minimal config target)",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           1,
				Revisions: []RevisionRollout{{
					RevisionName: "between-the-buttons",
					Percent:      1,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           1,
				Revisions: []RevisionRollout{{
					RevisionName: "bridges-to-babylon",
					Percent:      1,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           1,
				Revisions: []RevisionRollout{{
					RevisionName: "between-the-buttons",
					Percent:      1,
				}},
			}},
		},
	}, {
		name: "new tag, no roll", // just attached a tag to an existing route.
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}, {
				ConfigurationName: "mick",
				Tag:               "jagger",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}, {
				ConfigurationName: "mick",
				Tag:               "jagger",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}},
		},
	}, {
		name: "deleted config, no roll",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      100,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      50,
				}},
			}, {
				ConfigurationName: "mick",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      50,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      100,
				}},
			}},
		},
	}, {
		name: "new config, no roll, newer smaller",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      50,
				}},
			}, {
				ConfigurationName: "mick",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      50,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "mick",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      100,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      50,
				}},
			}, {
				ConfigurationName: "mick",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      50,
				}},
			}},
		},
	}, {
		name: "new config, no roll, newer larger",
		cur: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      50,
				}},
			}, {
				ConfigurationName: "mick",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      50,
				}},
			}},
		},
		prev: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           100,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      100,
				}},
			}},
		},
		want: &Rollout{
			Configurations: []ConfigurationRollout{{
				ConfigurationName: "keith",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "black-on-blue",
					Percent:      50,
				}},
			}, {
				ConfigurationName: "mick",
				Percent:           50,
				Revisions: []RevisionRollout{{
					RevisionName: "it's-only-rock-n-roll",
					Percent:      50,
				}},
			}},
		},
	}}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got, want := tc.cur.Step(tc.prev), tc.want; !cmp.Equal(got, want) {
				t.Errorf("Wrong rolled rollout, diff(-want,+got):\n%s", cmp.Diff(want, got))
			}
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package storage

import (
	"context"
	"net"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apiserver/pkg/registry/generic"
	genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
	"k8s.io/apiserver/pkg/registry/rest"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	api "k8s.io/kubernetes/pkg/apis/core"
	"k8s.io/kubernetes/pkg/features"
	"k8s.io/kubernetes/pkg/printers"
	printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
	printerstorage "k8s.io/kubernetes/pkg/printers/storage"
	"k8s.io/kubernetes/pkg/registry/core/service"
	registry "k8s.io/kubernetes/pkg/registry/core/service"
	svcreg "k8s.io/kubernetes/pkg/registry/core/service"
	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"

	netutil "k8s.io/utils/net"
)

type GenericREST struct {
	*genericregistry.Store
	primaryIPFamily *api.IPFamily
	secondaryFamily *api.IPFamily
}

// NewGenericREST returns a RESTStorage object that will work against services.
func NewGenericREST(optsGetter generic.RESTOptionsGetter, serviceCIDR net.IPNet, hasSecondary bool) (*GenericREST, *StatusREST, error) {
	strategy, _ := registry.StrategyForServiceCIDRs(serviceCIDR, hasSecondary)

	store := &genericregistry.Store{
		NewFunc:                  func() runtime.Object { return &api.Service{} },
		NewListFunc:              func() runtime.Object { return &api.ServiceList{} },
		DefaultQualifiedResource: api.Resource("services"),
		ReturnDeletedObject:      true,

		CreateStrategy:      strategy,
		UpdateStrategy:      strategy,
		DeleteStrategy:      strategy,
		ResetFieldsStrategy: strategy,

		TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
	}
	options := &generic.StoreOptions{RESTOptions: optsGetter}
	if err := store.CompleteWithOptions(options); err != nil {
		return nil, nil, err
	}

	statusStore := *store
	statusStrategy := service.NewServiceStatusStrategy(strategy)
	statusStore.UpdateStrategy = statusStrategy
	statusStore.ResetFieldsStrategy = statusStrategy

	ipv4 := api.IPv4Protocol
	ipv6 := api.IPv6Protocol
	var primaryIPFamily *api.IPFamily
	var secondaryFamily *api.IPFamily
	if netutil.IsIPv6CIDR(&serviceCIDR) {
		primaryIPFamily = &ipv6
		if hasSecondary {
			secondaryFamily = &ipv4
		}
	} else {
		primaryIPFamily = &ipv4
		if hasSecondary {
			secondaryFamily = &ipv6
		}
	}
	genericStore := &GenericREST{store, primaryIPFamily, secondaryFamily}
	store.Decorator = genericStore.defaultOnRead

	return genericStore, &StatusREST{store: &statusStore}, nil
}

var (
	_ rest.ShortNamesProvider = &GenericREST{}
	_ rest.CategoriesProvider = &GenericREST{}
)

// ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource.
func (r *GenericREST) ShortNames() []string {
	return []string{"svc"}
}

// Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of.
func (r *GenericREST) Categories() []string {
	return []string{"all"}
}

// StatusREST implements the GenericREST endpoint for changing the status of a service.
type StatusREST struct {
	store *genericregistry.Store
}

func (r *StatusREST) New() runtime.Object {
	return &api.Service{}
}

// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.store.Get(ctx, name, options)
}

// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
	// subresources should never allow create on update.
	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}

// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return r.store.GetResetFields()
}

// defaultOnRead sets interlinked fields that were not previously set on read.
// We can't do this in the normal defaulting path because that same logic
// applies on Get, Create, and Update, but we need to distinguish between them.
//
// This will be called on both Service and ServiceList types.
func (r *GenericREST) defaultOnRead(obj runtime.Object) {
	switch s := obj.(type) {
	case *api.Service:
		r.defaultOnReadService(s)
	case *api.ServiceList:
		r.defaultOnReadServiceList(s)
	default:
		// This was not an object we can default.  This is not an error, as the
		// caching layer can pass through here, too.
	}
}

// defaultOnReadServiceList defaults a ServiceList.
func (r *GenericREST) defaultOnReadServiceList(serviceList *api.ServiceList) {
	if serviceList == nil {
		return
	}

	for i := range serviceList.Items {
		r.defaultOnReadService(&serviceList.Items[i])
	}
}

// defaultOnReadService defaults a single Service.
func (r *GenericREST) defaultOnReadService(service *api.Service) {
	if service == nil {
		return
	}

	// We might find Services that were written before ClusterIP became plural.
	// We still want to present a consistent view of them.
	// NOTE: the args are (old, new)
	svcreg.NormalizeClusterIPs(nil, service)

	// The rest of this does not apply unless dual-stack is enabled.
	if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
		return
	}

	if len(service.Spec.IPFamilies) > 0 {
		return // already defaulted
	}

	// set clusterIPs based on ClusterIP
	if len(service.Spec.ClusterIPs) == 0 {
		if len(service.Spec.ClusterIP) > 0 {
			service.Spec.ClusterIPs = []string{service.Spec.ClusterIP}
		}
	}

	requireDualStack := api.IPFamilyPolicyRequireDualStack
	singleStack := api.IPFamilyPolicySingleStack
	preferDualStack := api.IPFamilyPolicyPreferDualStack
	// headless services
	if len(service.Spec.ClusterIPs) == 1 && service.Spec.ClusterIPs[0] == api.ClusterIPNone {
		service.Spec.IPFamilies = []api.IPFamily{*r.primaryIPFamily}

		// headless+selectorless
		// headless+selectorless takes both families. Why?
		// at this stage we don't know what kind of endpoints (specifically their IPFamilies) the
		// user has assigned to this selectorless service. We assume it has dualstack and we default
		// it to PreferDualStack on any cluster (single or dualstack configured).
		if len(service.Spec.Selector) == 0 {
			service.Spec.IPFamilyPolicy = &preferDualStack
			if *r.primaryIPFamily == api.IPv4Protocol {
				service.Spec.IPFamilies = append(service.Spec.IPFamilies, api.IPv6Protocol)
			} else {
				service.Spec.IPFamilies = append(service.Spec.IPFamilies, api.IPv4Protocol)
			}
		} else {
			// headless w/ selector
			// this service type follows cluster configuration. this service (selector based) uses a
			// selector and will have to follow how the cluster is configured. If the cluster is
			// configured to dual stack then the service defaults to PreferDualStack. Otherwise we
			// default it to SingleStack.
			if r.secondaryFamily != nil {
				service.Spec.IPFamilies = append(service.Spec.IPFamilies, *r.secondaryFamily)
				service.Spec.IPFamilyPolicy = &preferDualStack
			} else {
				service.Spec.IPFamilyPolicy = &singleStack
			}
		}
	} else {
		// headful
		// make sure a slice exists to receive the families
		service.Spec.IPFamilies = make([]api.IPFamily, len(service.Spec.ClusterIPs), len(service.Spec.ClusterIPs))
		for idx, ip := range service.Spec.ClusterIPs {
			if netutil.IsIPv6String(ip) {
				service.Spec.IPFamilies[idx] = api.IPv6Protocol
			} else {
				service.Spec.IPFamilies[idx] = api.IPv4Protocol
			}

			if len(service.Spec.IPFamilies) == 1 {
				service.Spec.IPFamilyPolicy = &singleStack
			} else if len(service.Spec.IPFamilies) == 2 {
				service.Spec.IPFamilyPolicy = &requireDualStack
			}
		}
	}
}
<commit_msg>Svc REST: Add stub begin* hooks<commit_after>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package storage

import (
	"context"
	"net"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apiserver/pkg/registry/generic"
	genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
	"k8s.io/apiserver/pkg/registry/rest"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	api "k8s.io/kubernetes/pkg/apis/core"
	"k8s.io/kubernetes/pkg/features"
	"k8s.io/kubernetes/pkg/printers"
	printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
	printerstorage "k8s.io/kubernetes/pkg/printers/storage"
	"k8s.io/kubernetes/pkg/registry/core/service"
	registry "k8s.io/kubernetes/pkg/registry/core/service"
	svcreg "k8s.io/kubernetes/pkg/registry/core/service"
	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"

	netutil "k8s.io/utils/net"
)

type GenericREST struct {
	*genericregistry.Store
	primaryIPFamily *api.IPFamily
	secondaryFamily *api.IPFamily
}

// NewGenericREST returns a RESTStorage object that will work against services.
func NewGenericREST(optsGetter generic.RESTOptionsGetter, serviceCIDR net.IPNet, hasSecondary bool) (*GenericREST, *StatusREST, error) {
	strategy, _ := registry.StrategyForServiceCIDRs(serviceCIDR, hasSecondary)

	store := &genericregistry.Store{
		NewFunc:                  func() runtime.Object { return &api.Service{} },
		NewListFunc:              func() runtime.Object { return &api.ServiceList{} },
		DefaultQualifiedResource: api.Resource("services"),
		ReturnDeletedObject:      true,

		CreateStrategy:      strategy,
		UpdateStrategy:      strategy,
		DeleteStrategy:      strategy,
		ResetFieldsStrategy: strategy,

		TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
	}
	options := &generic.StoreOptions{RESTOptions: optsGetter}
	if err := store.CompleteWithOptions(options); err != nil {
		return nil, nil, err
	}

	statusStore := *store
	statusStrategy := service.NewServiceStatusStrategy(strategy)
	statusStore.UpdateStrategy = statusStrategy
	statusStore.ResetFieldsStrategy = statusStrategy

	ipv4 := api.IPv4Protocol
	ipv6 := api.IPv6Protocol
	var primaryIPFamily *api.IPFamily
	var secondaryFamily *api.IPFamily
	if netutil.IsIPv6CIDR(&serviceCIDR) {
		primaryIPFamily = &ipv6
		if hasSecondary {
			secondaryFamily = &ipv4
		}
	} else {
		primaryIPFamily = &ipv4
		if hasSecondary {
			secondaryFamily = &ipv6
		}
	}
	genericStore := &GenericREST{store, primaryIPFamily, secondaryFamily}
	store.Decorator = genericStore.defaultOnRead
	store.BeginCreate = genericStore.beginCreate
	store.BeginUpdate = genericStore.beginUpdate

	return genericStore, &StatusREST{store: &statusStore}, nil
}

var (
	_ rest.ShortNamesProvider = &GenericREST{}
	_ rest.CategoriesProvider = &GenericREST{}
)

// ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource.
func (r *GenericREST) ShortNames() []string {
	return []string{"svc"}
}

// Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of.
func (r *GenericREST) Categories() []string {
	return []string{"all"}
}

// StatusREST implements the GenericREST endpoint for changing the status of a service.
type StatusREST struct {
	store *genericregistry.Store
}

func (r *StatusREST) New() runtime.Object {
	return &api.Service{}
}

// Get retrieves the object from the storage. It is required to support Patch.
func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
	return r.store.Get(ctx, name, options)
}

// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
	// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
	// subresources should never allow create on update.
	return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}

// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	return r.store.GetResetFields()
}

// defaultOnRead sets interlinked fields that were not previously set on read.
// We can't do this in the normal defaulting path because that same logic
// applies on Get, Create, and Update, but we need to distinguish between them.
//
// This will be called on both Service and ServiceList types.
func (r *GenericREST) defaultOnRead(obj runtime.Object) {
	switch s := obj.(type) {
	case *api.Service:
		r.defaultOnReadService(s)
	case *api.ServiceList:
		r.defaultOnReadServiceList(s)
	default:
		// This was not an object we can default.  This is not an error, as the
		// caching layer can pass through here, too.
	}
}

// defaultOnReadServiceList defaults a ServiceList.
func (r *GenericREST) defaultOnReadServiceList(serviceList *api.ServiceList) {
	if serviceList == nil {
		return
	}

	for i := range serviceList.Items {
		r.defaultOnReadService(&serviceList.Items[i])
	}
}

// defaultOnReadService defaults a single Service.
func (r *GenericREST) defaultOnReadService(service *api.Service) {
	if service == nil {
		return
	}

	// We might find Services that were written before ClusterIP became plural.
	// We still want to present a consistent view of them.
	// NOTE: the args are (old, new)
	svcreg.NormalizeClusterIPs(nil, service)

	// The rest of this does not apply unless dual-stack is enabled.
	if !utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) {
		return
	}

	if len(service.Spec.IPFamilies) > 0 {
		return // already defaulted
	}

	// set clusterIPs based on ClusterIP
	if len(service.Spec.ClusterIPs) == 0 {
		if len(service.Spec.ClusterIP) > 0 {
			service.Spec.ClusterIPs = []string{service.Spec.ClusterIP}
		}
	}

	requireDualStack := api.IPFamilyPolicyRequireDualStack
	singleStack := api.IPFamilyPolicySingleStack
	preferDualStack := api.IPFamilyPolicyPreferDualStack
	// headless services
	if len(service.Spec.ClusterIPs) == 1 && service.Spec.ClusterIPs[0] == api.ClusterIPNone {
		service.Spec.IPFamilies = []api.IPFamily{*r.primaryIPFamily}

		// headless+selectorless
		// headless+selectorless takes both families. Why?
		// at this stage we don't know what kind of endpoints (specifically their IPFamilies) the
		// user has assigned to this selectorless service. We assume it has dualstack and we default
		// it to PreferDualStack on any cluster (single or dualstack configured).
		if len(service.Spec.Selector) == 0 {
			service.Spec.IPFamilyPolicy = &preferDualStack
			if *r.primaryIPFamily == api.IPv4Protocol {
				service.Spec.IPFamilies = append(service.Spec.IPFamilies, api.IPv6Protocol)
			} else {
				service.Spec.IPFamilies = append(service.Spec.IPFamilies, api.IPv4Protocol)
			}
		} else {
			// headless w/ selector
			// this service type follows cluster configuration. this service (selector based) uses a
			// selector and will have to follow how the cluster is configured. If the cluster is
			// configured to dual stack then the service defaults to PreferDualStack. Otherwise we
			// default it to SingleStack.
			if r.secondaryFamily != nil {
				service.Spec.IPFamilies = append(service.Spec.IPFamilies, *r.secondaryFamily)
				service.Spec.IPFamilyPolicy = &preferDualStack
			} else {
				service.Spec.IPFamilyPolicy = &singleStack
			}
		}
	} else {
		// headful
		// make sure a slice exists to receive the families
		service.Spec.IPFamilies = make([]api.IPFamily, len(service.Spec.ClusterIPs), len(service.Spec.ClusterIPs))
		for idx, ip := range service.Spec.ClusterIPs {
			if netutil.IsIPv6String(ip) {
				service.Spec.IPFamilies[idx] = api.IPv6Protocol
			} else {
				service.Spec.IPFamilies[idx] = api.IPv4Protocol
			}

			if len(service.Spec.IPFamilies) == 1 {
				service.Spec.IPFamilyPolicy = &singleStack
			} else if len(service.Spec.IPFamilies) == 2 {
				service.Spec.IPFamilyPolicy = &requireDualStack
			}
		}
	}
}

func (r *GenericREST) beginCreate(ctx context.Context, obj runtime.Object, options *metav1.CreateOptions) (genericregistry.FinishFunc, error) {
	svc := obj.(*api.Service)

	// FIXME: remove this when implementing
	_ = svc

	// Our cleanup callback
	finish := func(_ context.Context, success bool) {
		if success {
		} else {
		}
	}

	return finish, nil
}

func (r *GenericREST) beginUpdate(ctx context.Context, obj, oldObj runtime.Object, options *metav1.UpdateOptions) (genericregistry.FinishFunc, error) {
	newSvc := obj.(*api.Service)
	oldSvc := oldObj.(*api.Service)

	// FIXME: remove these when implementing
	_ = oldSvc
	_ = newSvc

	// Our cleanup callback
	finish := func(_ context.Context, success bool) {
		if success {
		} else {
		}
	}

	return finish, nil
}
<|endoftext|>


<commit_before>package qshell

import (
	"bufio"
	"fmt"
	"github.com/qiniu/log"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type DirCache struct {
}

func (this *DirCache) Cache(cacheRootPath string, cacheResultFile string) (fileCount int) {
	if _, err := os.Stat(cacheResultFile); err != nil {
		log.Info(fmt.Sprintf("No cache file `%s' found, will create one", cacheResultFile))
	} else {
		os.Remove(cacheResultFile + ".old")
		if rErr := os.Rename(cacheResultFile, cacheResultFile+".old"); rErr != nil {
			log.Error(fmt.Sprintf("Unable to rename cache file, plz manually delete `%s' and `%s.old'",
				cacheResultFile, cacheResultFile))
			log.Error(rErr)
			return
		}
	}
	cacheResultFileH, err := os.OpenFile(cacheResultFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
	if err != nil {
		log.Error(fmt.Sprintf("Failed to open cache file `%s'", cacheResultFile))
		return
	}
	defer cacheResultFileH.Close()
	bWriter := bufio.NewWriter(cacheResultFileH)
	walkStart := time.Now()
	log.Info(fmt.Sprintf("Walk `%s' start from `%s'", cacheRootPath, walkStart.String()))
	filepath.Walk(cacheRootPath, func(path string, fi os.FileInfo, err error) error {
		var retErr error
		//log.Debug(fmt.Sprintf("Walking through `%s'", cacheRootPath))
		if !fi.IsDir() {
			relPath := strings.TrimPrefix(strings.TrimPrefix(path, cacheRootPath), string(os.PathSeparator))
			fsize := fi.Size()
			//Unit is 100ns
			flmd := fi.ModTime().UnixNano() / 100
			//log.Debug(fmt.Sprintf("Hit file `%s' size: `%d' mode time: `%d`", relPath, fsize, flmd))
			fmeta := fmt.Sprintln(fmt.Sprintf("%s\t%d\t%d", relPath, fsize, flmd))
			if _, err := bWriter.WriteString(fmeta); err != nil {
				log.Error(fmt.Sprintf("Failed to write data `%s' to cache file", fmeta))
				retErr = err
			}
			fileCount += 1
		}
		return retErr
	})
	if err := bWriter.Flush(); err != nil {
		log.Error(fmt.Sprintf("Failed to flush to cache file `%s'", cacheResultFile))
	}

	walkEnd := time.Now()
	log.Info(fmt.Sprintf("Walk `%s' end at `%s'", cacheRootPath, walkEnd.String()))
	log.Info(fmt.Sprintf("Walk `%s' last for `%s'", cacheRootPath, time.Since(walkStart)))
	return
}
<commit_msg>fix dir cache bug, handle the walk error<commit_after>package qshell

import (
	"bufio"
	"fmt"
	"github.com/qiniu/log"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type DirCache struct {
}

func (this *DirCache) Cache(cacheRootPath string, cacheResultFile string) (fileCount int64) {
	if _, err := os.Stat(cacheResultFile); err != nil {
		log.Info(fmt.Sprintf("No cache file `%s' found, will create one", cacheResultFile))
	} else {
		os.Remove(cacheResultFile + ".old")
		if rErr := os.Rename(cacheResultFile, cacheResultFile+".old"); rErr != nil {
			log.Error(fmt.Sprintf("Unable to rename cache file, plz manually delete `%s' and `%s.old'",
				cacheResultFile, cacheResultFile))
			log.Error(rErr)
			return
		}
	}
	cacheResultFileH, err := os.OpenFile(cacheResultFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
	if err != nil {
		log.Error(fmt.Sprintf("Failed to open cache file `%s'", cacheResultFile))
		return
	}
	defer cacheResultFileH.Close()
	bWriter := bufio.NewWriter(cacheResultFileH)
	walkStart := time.Now()
	log.Info(fmt.Sprintf("Walk `%s' start from `%s'", cacheRootPath, walkStart.String()))
	filepath.Walk(cacheRootPath, func(path string, fi os.FileInfo, err error) error {
		var retErr error
		//log.Debug(fmt.Sprintf("Walking through `%s'", cacheRootPath))
		if err != nil {
			retErr = err
		} else {
			if !fi.IsDir() {
				relPath := strings.TrimPrefix(strings.TrimPrefix(path, cacheRootPath), string(os.PathSeparator))
				fsize := fi.Size()
				//Unit is 100ns
				flmd := fi.ModTime().UnixNano() / 100
				//log.Debug(fmt.Sprintf("Hit file `%s' size: `%d' mode time: `%d`", relPath, fsize, flmd))
				fmeta := fmt.Sprintln(fmt.Sprintf("%s\t%d\t%d", relPath, fsize, flmd))
				if _, err := bWriter.WriteString(fmeta); err != nil {
					log.Error(fmt.Sprintf("Failed to write data `%s' to cache file", fmeta))
					retErr = err
				}
				fileCount += 1
			}
		}
		return retErr
	})
	if err := bWriter.Flush(); err != nil {
		log.Error(fmt.Sprintf("Failed to flush to cache file `%s'", cacheResultFile))
	}

	walkEnd := time.Now()
	log.Info(fmt.Sprintf("Walk `%s' end at `%s'", cacheRootPath, walkEnd.String()))
	log.Info(fmt.Sprintf("Walk `%s' last for `%s'", cacheRootPath, time.Since(walkStart)))
	return
}
<|endoftext|>


<commit_before>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import "encoding"

// BlockID is the (usually content-based) ID for a data block.
type BlockID struct {
	h Hash
}

var _ encoding.BinaryMarshaler = BlockID{}
var _ encoding.BinaryUnmarshaler = (*BlockID)(nil)

// MaxBlockIDStringLength is the maximum length of the string
// representation of a BlockID.
const MaxBlockIDStringLength = MaxHashStringLength

// BlockIDFromString creates a BlockID from the given string. If the
// returned error is nil, the returned BlockID is valid.
func BlockIDFromString(dataStr string) (BlockID, error) {
	h, err := HashFromString(dataStr)
	if err != nil {
		return BlockID{}, err
	}
	return BlockID{h}, nil
}

// IsValid returns whether the block ID is valid. A zero block ID is
// considered invalid.
func (id BlockID) IsValid() bool {
	return id.h.IsValid()
}

// Bytes returns the bytes of the block ID.
func (id BlockID) Bytes() []byte {
	return id.h.Bytes()
}

func (id BlockID) String() string {
	return id.h.String()
}

// MarshalBinary implements the encoding.BinaryMarshaler interface for
// BlockID. Returns an error if the BlockID is invalid and not the zero
// BlockID.
func (id BlockID) MarshalBinary() (data []byte, err error) {
	return id.h.MarshalBinary()
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
// for BlockID. Returns an error if the given byte array is non-empty and
// the BlockID is invalid.
func (id *BlockID) UnmarshalBinary(data []byte) error {
	return id.h.UnmarshalBinary(data)
}
<commit_msg>block_id: implement encoding.json.Marshaler<commit_after>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"encoding"
	"fmt"
	"strings"
)

// BlockID is the (usually content-based) ID for a data block.
type BlockID struct {
	h Hash
}

var _ encoding.BinaryMarshaler = BlockID{}
var _ encoding.BinaryUnmarshaler = (*BlockID)(nil)

// MaxBlockIDStringLength is the maximum length of the string
// representation of a BlockID.
const MaxBlockIDStringLength = MaxHashStringLength

// BlockIDFromString creates a BlockID from the given string. If the
// returned error is nil, the returned BlockID is valid.
func BlockIDFromString(dataStr string) (BlockID, error) {
	h, err := HashFromString(dataStr)
	if err != nil {
		return BlockID{}, err
	}
	return BlockID{h}, nil
}

// IsValid returns whether the block ID is valid. A zero block ID is
// considered invalid.
func (id BlockID) IsValid() bool {
	return id.h.IsValid()
}

// Bytes returns the bytes of the block ID.
func (id BlockID) Bytes() []byte {
	return id.h.Bytes()
}

func (id BlockID) String() string {
	return id.h.String()
}

// MarshalBinary implements the encoding.BinaryMarshaler interface for
// BlockID. Returns an error if the BlockID is invalid and not the zero
// BlockID.
func (id BlockID) MarshalBinary() (data []byte, err error) {
	return id.h.MarshalBinary()
}

// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface
// for BlockID. Returns an error if the given byte array is non-empty and
// the BlockID is invalid.
func (id *BlockID) UnmarshalBinary(data []byte) error {
	return id.h.UnmarshalBinary(data)
}

// MarshalJSON implements the encoding.json.Marshaler interface for
// BlockID.
func (id BlockID) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf("\"%s\"", id)), nil
}

// UnmarshalJSON implements the encoding.json.Unmarshaler interface
// for BlockID.
func (id BlockID) UnmarshalJSON(s []byte) error {
	blockIDStr := strings.Trim(string(s), "\"")
	newID, err := BlockIDFromString(blockIDStr)
	if err != nil {
		return err
	}
	id.h = newID.h
	return nil
}
<|endoftext|>


<commit_before>// +build linux

package libsysinfo

import (
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

const ()

var (
	cacheKeys = map[string]string{
		"HOSTNAME_FULL":        "fullhostname",
		"HOSTNAME":             "hostname",
		"DOMAIN_NAME":          "domainname",
		"LSB_FULL":             "lsbfull",
		"LSB_DIST_CODE_NAME":   "lsbdistcodename",
		"LSB_DIST_DESCRIPTION": "lsbdistdescrption",
		"LSB_DIST_ID":          "lsbdistid",
		"LSB_DIST_RELEASE":     "lsbdistrelease",
		"HOST_ID":              "hostid",
		"FILE_SYSTEMS":         "filesystems",
	}

	globalCache           = newCachedValues(len(cacheKeys))
	fileSystemCache       []string
	cpuInfoCache          []CpuInfo
	networkInterfaceCache []NetworkInterface

	ErrDomainNameNotFound = &LibSysInfoErr{"Domain name not found"}
	ErrNoNetIfaceFound    = &LibSysInfoErr{"No network interface found"}
	ErrIfConfigNotFound   = &LibSysInfoErr{"No ifconfig command found"}
)

// ----

type CpuInfo struct {
	Processor      string
	VendorId       string
	CpuFamily      string
	Model          string
	ModelName      string
	Stepping       string
	CPUMHz         string
	CacheSize      string
	CacheSizeUnit  string
	PhysicalId     string
	Siblings       string
	CoreId         string
	CpuCores       string
	ApicId         string
	InitialApicId  string
	Fpu            string
	FpuException   string
	CpuIdLevel     string
	Wp             string
	Flags          []string
	Bogomips       string
	ClflushSize    string
	CacheAlignment string
	AddressSizes   string
}

type LsbReleaseInfo struct {
	Codename      string
	Description   string
	DistributorId string
	Release       string
}

type NetworkInterface struct {
	Name          string
	V4Addr        string
	V6Addr        string
	MacAddr       string
	BroadcastAddr string
	NetMask       string
}

// ----

func Hostname() (string, error) {
	llv := &lazyLoadedValue{
		CacheKey:    cacheKeys["HOSTNAME"],
		Fetcher:     getFullHostname,
		Processor:   processHostname,
		CacheBucket: globalCache,
	}

	return llv.run()
}

func Domain() (string, error) {
	llv := &lazyLoadedValue{
		CacheKey:    cacheKeys["DOMAIN_NAME"],
		Fetcher:     getFullHostname,
		Processor:   processDomainName,
		CacheBucket: globalCache,
	}

	return llv.run()
}

func Fqdn() (string, error) {
	fqdn := func(fullHostname string) (string, error) {
		return fullHostname, nil
	}

	llv := &lazyLoadedValue{
		CacheKey:    cacheKeys["FQDN"],
		Fetcher:     getFullHostname,
		Processor:   fqdn,
		CacheBucket: globalCache,
	}

	return llv.run()
}

func LsbRelease() (LsbReleaseInfo, error) {
	var lsbr LsbReleaseInfo

	v, err := lsbReleaseItem("LSB_DIST_CODE_NAME", "Codename")
	if err != nil {
		return lsbr, err
	}
	lsbr.Codename = v

	v, err = lsbReleaseItem("LSB_DIST_DESCRIPTION", "Description")
	if err != nil {
		return lsbr, err
	}
	lsbr.Description = v

	v, err = lsbReleaseItem("LSB_DIST_ID", "Distributor ID")
	if err != nil {
		return lsbr, err
	}
	lsbr.DistributorId = v

	v, err = lsbReleaseItem("LSB_DIST_RELEASE", "Release")
	if err != nil {
		return lsbr, err
	}
	lsbr.Release = v

	return lsbr, nil
}

func HostId() (string, error) {
	llv := &lazyLoadedValue{
		CacheKey:    cacheKeys["HOST_ID"],
		Fetcher:     getHostId,
		Processor:   processHostId,
		CacheBucket: globalCache,
	}

	return llv.run()
}

func FileSystems() ([]string, error) {
	if len(fileSystemCache) > 0 {
		return fileSystemCache, nil
	}

	buff, err := getFileSystems()
	if err != nil {
		return fileSystemCache, err
	}

	return processFileSystems(buff), nil
}

func CpuInfos() ([]CpuInfo, error) {
	if len(cpuInfoCache) > 0 {
		return cpuInfoCache, nil
	}

	buff, err := getCpuInfos()
	if err != nil {
		return []CpuInfo(nil), err
	}

	return processCpuInfos(buff), nil
}

func NetworkInterfaces() ([]NetworkInterface, error) {
	// XXX : switch to a cgo/iotctl based implementation
	// XXX : parsing ifconfig's result is a PITA

	if len(networkInterfaceCache) > 0 {
		return networkInterfaceCache, nil
	}

	var ifaces []NetworkInterface
	devices, err := findNetworkDevices()
	if err != nil {
		return []NetworkInterface{}, err
	}
	if len(devices) <= 0 {
		return ifaces, ErrNoNetIfaceFound
	}

	ifConfig, err := findIfconfig()
	if err != nil {
		return ifaces, err
	}

	for _, d := range devices {
		out, err := exec.Command(ifConfig, d).Output()
		if err != nil {
			return ifaces, err
		}

		ifaces = append(ifaces, processIfconfigOutput(d, string(out)))
	}

	return ifaces, nil
}

func findNetworkDevices() ([]string, error) {
	var devs []string

	f, err := os.Open("/sys/class/net/")
	if err != nil {
		return devs, err
	}
	defer f.Close()

	allNames := -1
	names, err := f.Readdirnames(allNames)
	if err != nil {
		return devs, err
	}

	if len(names) <= 0 {
		return names, ErrNoNetIfaceFound
	}

	return names, nil
}

func findIfconfig() (string, error) {
	possiblePaths := []string{
		"/sbin/ifconfig",
		"/bin/ifconfig",
		"/usr/sbin/ifconfig",
	}

	var f *os.File
	var err error

	for _, path := range possiblePaths {
		f, err = os.Open(path)
		if os.IsNotExist(err) {
			continue
		}
		defer f.Close()

		return path, nil
	}

	return "", ErrIfConfigNotFound
}

// ----

func lsbReleaseItem(k string, lsbItem string) (string, error) {
	proc := func(lsb string) (string, error) {
		return processLsbItem(lsb, lsbItem)
	}

	llv := &lazyLoadedValue{
		CacheKey:    cacheKeys[k],
		Fetcher:     getLsbRelease,
		Processor:   proc,
		CacheBucket: globalCache,
	}

	return llv.run()
}

func processDomainName(fullHostname string) (string, error) {
	pos := strings.Index(fullHostname, ".")
	if pos == -1 {
		return "", ErrDomainNameNotFound
	}

	return fullHostname[pos+1:], nil
}

func processHostname(fullHostname string) (string, error) {
	pos := strings.Index(fullHostname, ".")
	if pos == -1 {
		return fullHostname, nil
	}

	return fullHostname[:pos], nil
}

func processLsbItem(lsb string, item string) (string, error) {
	var out string
	var tmp string

	for _, line := range strings.Split(lsb, "\n") {
		if len(line) <= 0 {
			continue
		}

		tmp = line[0:len(item)]
		if tmp == item {
			out = strings.TrimSpace(strings.TrimLeft(line, item+":"))
			break
		}
	}

	return strings.ToLower(out), nil
}

func processHostId(id string) (string, error) {
	return strings.Trim(id, "\n"), nil
}

func processFileSystems(buff string) []string {
	var tmp string
	var fileSystems []string
	var isNodev bool

	for _, line := range strings.Split(buff, "\n") {
		isNodev = len(line) <= 0 || line[0] == 'n'
		if isNodev {
			continue
		}

		tmp = strings.TrimSpace(line)
		if len(tmp) <= 0 {
			continue
		}

		fileSystems = append(fileSystems, tmp)
	}

	return fileSystems
}

func processCpuInfos(buff string) []CpuInfo {
	var parts []string
	var k, v string
	var cpuInfos []CpuInfo
	var tmp CpuInfo

	lines := strings.Split(buff, "\n")
	lineCount := len(lines)

	for i, line := range lines {
		if line == "" {
			// extra empty lines means end of file
			if i+1 == lineCount {
				break
			}

			cpuInfos = append(cpuInfos, tmp)
			tmp = CpuInfo{}
			continue
		}

		parts = strings.Split(line, ":")
		if len(parts) == 2 {
			k = strings.ToLower(strings.TrimSpace(parts[0]))
			v = strings.TrimSpace(parts[1])
			if v == "" {
				continue
			}

			switch k {
			case "processor":
				tmp.Processor = v
			case "vendor_id":
				tmp.VendorId = v
			case "cpu family":
				tmp.CpuFamily = v
			case "model":
				tmp.Model = v
			case "model name":
				tmp.ModelName = v
			case "stepping":
				tmp.Stepping = v
			case "cpu mhz":
				tmp.CPUMHz = v
			case "cache size":
				cacheSize := strings.Split(v, " ")
				tmp.CacheSize = cacheSize[0]
				tmp.CacheSizeUnit = cacheSize[1]
			case "physical id":
				tmp.PhysicalId = v
			case "siblings":
				tmp.Siblings = v
			case "core id":
				tmp.CoreId = v
			case "cpu cores":
				tmp.CpuCores = v
			case "apicid":
				tmp.ApicId = v
			case "initial apicid":
				tmp.InitialApicId = v
			case "fpu":
				tmp.Fpu = v
			case "fpu_exception":
				tmp.FpuException = v
			case "cpuid level":
				tmp.CpuIdLevel = v
			case "wp":
				tmp.Wp = v
			case "flags":
				tmp.Flags = strings.Split(v, " ")
			case "bogomips":
				tmp.Bogomips = v
			case "clflush size":
				tmp.ClflushSize = v
			case "cache_alignment":
				tmp.CacheAlignment = v
			case "address sizes":
				tmp.AddressSizes = v
			default:
				continue
			}
		}
	}

	return cpuInfos
}

func processIfconfigOutput(device string, out string) NetworkInterface {
	// XXX : this is so horrible ...
	const hwaddr = "HWaddr"
	var nif NetworkInterface

	lines := strings.Split(out, "\n")
	for _, line := range lines {
		if len(line) <= 0 {
			continue
		}
		isFirstLine := (line[0] != ' ')

		if isFirstLine && strings.Contains(line, hwaddr) {
			parts := strings.Split(line, hwaddr)
			nif.MacAddr = strings.TrimSpace(parts[len(parts)-1])
			continue
		}

		line = strings.TrimSpace(line)

		// i => inet or inet6
		if len(line) <= 0 || line[0] != 'i' {
			continue
		}

		fields := strings.Fields(line)
		if len(fields) <= 0 {
			continue
		}

		if fields[0] == "inet" {
			for _, f := range fields[1:] {
				if f[0] == 'a' {
					nif.V4Addr = strings.TrimPrefix(f, "addr:")
					continue
				}

				if f[0] == 'B' {
					nif.BroadcastAddr = strings.TrimPrefix(f, "Bcast:")
					continue
				}

				if f[0] == 'M' {
					nif.NetMask = strings.TrimPrefix(f, "Mask:")
					continue
				}
			}

			continue
		}

		if fields[0] == "inet6" {
			if len(fields) < 3 {
				continue
			}

			nif.V6Addr = fields[2]
		}
	}

	nif.Name = device

	return nif
}

// ----

func getFullHostname() (string, error) {
	cacheKey := cacheKeys["HOSTNAME_FULL"]

	hf, exists := globalCache.Exists(cacheKey)
	if exists {
		return hf, nil
	}

	out, err := exec.Command("hostname", "-f").Output()
	if err != nil {
		return string(out), err
	}

	hf = string(out)

	// removing \n
	pos := strings.Index(hf, "\n")
	if pos > -1 {
		hf = hf[:pos]
	}

	globalCache.Set(cacheKey, hf)

	return hf, nil
}

func getLsbRelease() (string, error) {
	cacheKey := cacheKeys["LSB_FULL"]

	lsb, exists := globalCache.Exists(cacheKey)
	if exists {
		return lsb, nil
	}

	out, err := exec.Command("lsb_release", "-a").Output()
	if err != nil {
		return string(out), err
	}

	lsb = string(out)

	globalCache.Set(cacheKey, lsb)

	return lsb, nil
}

func getHostId() (string, error) {
	// XXX : hostid will not be reused, no need to cache the full output
	out, err := exec.Command("hostid").Output()
	if err != nil {
		return string(out), err
	}

	return string(out), nil
}

func getFileSystems() (string, error) {
	buff, err := ioutil.ReadFile("/proc/filesystems")
	if err != nil {
		return "", nil
	}

	return string(buff), nil
}

func getCpuInfos() (string, error) {
	buff, err := ioutil.ReadFile("/proc/cpuinfo")
	if err != nil {
		return "", err
	}

	return string(buff), err
}

// ----

type LibSysInfoErr struct {
	Msg string
}

func (e LibSysInfoErr) Error() string {
	return e.Msg
}
<commit_msg>Moved functions<commit_after>// +build linux

package libsysinfo

import (
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

const ()

var (
	cacheKeys = map[string]string{
		"HOSTNAME_FULL":        "fullhostname",
		"HOSTNAME":             "hostname",
		"DOMAIN_NAME":          "domainname",
		"LSB_FULL":             "lsbfull",
		"LSB_DIST_CODE_NAME":   "lsbdistcodename",
		"LSB_DIST_DESCRIPTION": "lsbdistdescrption",
		"LSB_DIST_ID":          "lsbdistid",
		"LSB_DIST_RELEASE":     "lsbdistrelease",
		"HOST_ID":              "hostid",
		"FILE_SYSTEMS":         "filesystems",
	}

	globalCache           = newCachedValues(len(cacheKeys))
	fileSystemCache       []string
	cpuInfoCache          []CpuInfo
	networkInterfaceCache []NetworkInterface

	ErrDomainNameNotFound = &LibSysInfoErr{"Domain name not found"}
	ErrNoNetIfaceFound    = &LibSysInfoErr{"No network interface found"}
	ErrIfConfigNotFound   = &LibSysInfoErr{"No ifconfig command found"}
)

// ----

type CpuInfo struct {
	Processor      string
	VendorId       string
	CpuFamily      string
	Model          string
	ModelName      string
	Stepping       string
	CPUMHz         string
	CacheSize      string
	CacheSizeUnit  string
	PhysicalId     string
	Siblings       string
	CoreId         string
	CpuCores       string
	ApicId         string
	InitialApicId  string
	Fpu            string
	FpuException   string
	CpuIdLevel     string
	Wp             string
	Flags          []string
	Bogomips       string
	ClflushSize    string
	CacheAlignment string
	AddressSizes   string
}

type LsbReleaseInfo struct {
	Codename      string
	Description   string
	DistributorId string
	Release       string
}

type NetworkInterface struct {
	Name          string
	V4Addr        string
	V6Addr        string
	MacAddr       string
	BroadcastAddr string
	NetMask       string
}

// ----

func Hostname() (string, error) {
	llv := &lazyLoadedValue{
		CacheKey:    cacheKeys["HOSTNAME"],
		Fetcher:     getFullHostname,
		Processor:   processHostname,
		CacheBucket: globalCache,
	}

	return llv.run()
}

func Domain() (string, error) {
	llv := &lazyLoadedValue{
		CacheKey:    cacheKeys["DOMAIN_NAME"],
		Fetcher:     getFullHostname,
		Processor:   processDomainName,
		CacheBucket: globalCache,
	}

	return llv.run()
}

func Fqdn() (string, error) {
	fqdn := func(fullHostname string) (string, error) {
		return fullHostname, nil
	}

	llv := &lazyLoadedValue{
		CacheKey:    cacheKeys["FQDN"],
		Fetcher:     getFullHostname,
		Processor:   fqdn,
		CacheBucket: globalCache,
	}

	return llv.run()
}

func LsbRelease() (LsbReleaseInfo, error) {
	var lsbr LsbReleaseInfo

	v, err := lsbReleaseItem("LSB_DIST_CODE_NAME", "Codename")
	if err != nil {
		return lsbr, err
	}
	lsbr.Codename = v

	v, err = lsbReleaseItem("LSB_DIST_DESCRIPTION", "Description")
	if err != nil {
		return lsbr, err
	}
	lsbr.Description = v

	v, err = lsbReleaseItem("LSB_DIST_ID", "Distributor ID")
	if err != nil {
		return lsbr, err
	}
	lsbr.DistributorId = v

	v, err = lsbReleaseItem("LSB_DIST_RELEASE", "Release")
	if err != nil {
		return lsbr, err
	}
	lsbr.Release = v

	return lsbr, nil
}

func HostId() (string, error) {
	llv := &lazyLoadedValue{
		CacheKey:    cacheKeys["HOST_ID"],
		Fetcher:     getHostId,
		Processor:   processHostId,
		CacheBucket: globalCache,
	}

	return llv.run()
}

func FileSystems() ([]string, error) {
	if len(fileSystemCache) > 0 {
		return fileSystemCache, nil
	}

	buff, err := getFileSystems()
	if err != nil {
		return fileSystemCache, err
	}

	return processFileSystems(buff), nil
}

func CpuInfos() ([]CpuInfo, error) {
	if len(cpuInfoCache) > 0 {
		return cpuInfoCache, nil
	}

	buff, err := getCpuInfos()
	if err != nil {
		return []CpuInfo(nil), err
	}

	return processCpuInfos(buff), nil
}

func NetworkInterfaces() ([]NetworkInterface, error) {
	// XXX : switch to a cgo/iotctl based implementation
	// XXX : parsing ifconfig's result is a PITA

	if len(networkInterfaceCache) > 0 {
		return networkInterfaceCache, nil
	}

	var ifaces []NetworkInterface
	devices, err := findNetworkDevices()
	if err != nil {
		return []NetworkInterface{}, err
	}
	if len(devices) <= 0 {
		return ifaces, ErrNoNetIfaceFound
	}

	ifConfig, err := findIfconfig()
	if err != nil {
		return ifaces, err
	}

	for _, d := range devices {
		out, err := exec.Command(ifConfig, d).Output()
		if err != nil {
			return ifaces, err
		}

		ifaces = append(ifaces, processIfconfigOutput(d, string(out)))
	}

	return ifaces, nil
}

// ----

func findNetworkDevices() ([]string, error) {
	var devs []string

	f, err := os.Open("/sys/class/net/")
	if err != nil {
		return devs, err
	}
	defer f.Close()

	allNames := -1
	names, err := f.Readdirnames(allNames)
	if err != nil {
		return devs, err
	}

	if len(names) <= 0 {
		return names, ErrNoNetIfaceFound
	}

	return names, nil
}

func findIfconfig() (string, error) {
	possiblePaths := []string{
		"/sbin/ifconfig",
		"/bin/ifconfig",
		"/usr/sbin/ifconfig",
	}

	var f *os.File
	var err error

	for _, path := range possiblePaths {
		f, err = os.Open(path)
		if os.IsNotExist(err) {
			continue
		}
		defer f.Close()

		return path, nil
	}

	return "", ErrIfConfigNotFound
}

func lsbReleaseItem(k string, lsbItem string) (string, error) {
	proc := func(lsb string) (string, error) {
		return processLsbItem(lsb, lsbItem)
	}

	llv := &lazyLoadedValue{
		CacheKey:    cacheKeys[k],
		Fetcher:     getLsbRelease,
		Processor:   proc,
		CacheBucket: globalCache,
	}

	return llv.run()
}

func processDomainName(fullHostname string) (string, error) {
	pos := strings.Index(fullHostname, ".")
	if pos == -1 {
		return "", ErrDomainNameNotFound
	}

	return fullHostname[pos+1:], nil
}

func processHostname(fullHostname string) (string, error) {
	pos := strings.Index(fullHostname, ".")
	if pos == -1 {
		return fullHostname, nil
	}

	return fullHostname[:pos], nil
}

func processLsbItem(lsb string, item string) (string, error) {
	var out string
	var tmp string

	for _, line := range strings.Split(lsb, "\n") {
		if len(line) <= 0 {
			continue
		}

		tmp = line[0:len(item)]
		if tmp == item {
			out = strings.TrimSpace(strings.TrimLeft(line, item+":"))
			break
		}
	}

	return strings.ToLower(out), nil
}

func processHostId(id string) (string, error) {
	return strings.Trim(id, "\n"), nil
}

func processFileSystems(buff string) []string {
	var tmp string
	var fileSystems []string
	var isNodev bool

	for _, line := range strings.Split(buff, "\n") {
		isNodev = len(line) <= 0 || line[0] == 'n'
		if isNodev {
			continue
		}

		tmp = strings.TrimSpace(line)
		if len(tmp) <= 0 {
			continue
		}

		fileSystems = append(fileSystems, tmp)
	}

	return fileSystems
}

func processCpuInfos(buff string) []CpuInfo {
	var parts []string
	var k, v string
	var cpuInfos []CpuInfo
	var tmp CpuInfo

	lines := strings.Split(buff, "\n")
	lineCount := len(lines)

	for i, line := range lines {
		if line == "" {
			// extra empty lines means end of file
			if i+1 == lineCount {
				break
			}

			cpuInfos = append(cpuInfos, tmp)
			tmp = CpuInfo{}
			continue
		}

		parts = strings.Split(line, ":")
		if len(parts) == 2 {
			k = strings.ToLower(strings.TrimSpace(parts[0]))
			v = strings.TrimSpace(parts[1])
			if v == "" {
				continue
			}

			switch k {
			case "processor":
				tmp.Processor = v
			case "vendor_id":
				tmp.VendorId = v
			case "cpu family":
				tmp.CpuFamily = v
			case "model":
				tmp.Model = v
			case "model name":
				tmp.ModelName = v
			case "stepping":
				tmp.Stepping = v
			case "cpu mhz":
				tmp.CPUMHz = v
			case "cache size":
				cacheSize := strings.Split(v, " ")
				tmp.CacheSize = cacheSize[0]
				tmp.CacheSizeUnit = cacheSize[1]
			case "physical id":
				tmp.PhysicalId = v
			case "siblings":
				tmp.Siblings = v
			case "core id":
				tmp.CoreId = v
			case "cpu cores":
				tmp.CpuCores = v
			case "apicid":
				tmp.ApicId = v
			case "initial apicid":
				tmp.InitialApicId = v
			case "fpu":
				tmp.Fpu = v
			case "fpu_exception":
				tmp.FpuException = v
			case "cpuid level":
				tmp.CpuIdLevel = v
			case "wp":
				tmp.Wp = v
			case "flags":
				tmp.Flags = strings.Split(v, " ")
			case "bogomips":
				tmp.Bogomips = v
			case "clflush size":
				tmp.ClflushSize = v
			case "cache_alignment":
				tmp.CacheAlignment = v
			case "address sizes":
				tmp.AddressSizes = v
			default:
				continue
			}
		}
	}

	return cpuInfos
}

func processIfconfigOutput(device string, out string) NetworkInterface {
	// XXX : this is so horrible ...
	const hwaddr = "HWaddr"
	var nif NetworkInterface

	lines := strings.Split(out, "\n")
	for _, line := range lines {
		if len(line) <= 0 {
			continue
		}
		isFirstLine := (line[0] != ' ')

		if isFirstLine && strings.Contains(line, hwaddr) {
			parts := strings.Split(line, hwaddr)
			nif.MacAddr = strings.TrimSpace(parts[len(parts)-1])
			continue
		}

		line = strings.TrimSpace(line)

		// i => inet or inet6
		if len(line) <= 0 || line[0] != 'i' {
			continue
		}

		fields := strings.Fields(line)
		if len(fields) <= 0 {
			continue
		}

		if fields[0] == "inet" {
			for _, f := range fields[1:] {
				if f[0] == 'a' {
					nif.V4Addr = strings.TrimPrefix(f, "addr:")
					continue
				}

				if f[0] == 'B' {
					nif.BroadcastAddr = strings.TrimPrefix(f, "Bcast:")
					continue
				}

				if f[0] == 'M' {
					nif.NetMask = strings.TrimPrefix(f, "Mask:")
					continue
				}
			}

			continue
		}

		if fields[0] == "inet6" {
			if len(fields) < 3 {
				continue
			}

			nif.V6Addr = fields[2]
		}
	}

	nif.Name = device

	return nif
}

// ----

func getFullHostname() (string, error) {
	cacheKey := cacheKeys["HOSTNAME_FULL"]

	hf, exists := globalCache.Exists(cacheKey)
	if exists {
		return hf, nil
	}

	out, err := exec.Command("hostname", "-f").Output()
	if err != nil {
		return string(out), err
	}

	hf = string(out)

	// removing \n
	pos := strings.Index(hf, "\n")
	if pos > -1 {
		hf = hf[:pos]
	}

	globalCache.Set(cacheKey, hf)

	return hf, nil
}

func getLsbRelease() (string, error) {
	cacheKey := cacheKeys["LSB_FULL"]

	lsb, exists := globalCache.Exists(cacheKey)
	if exists {
		return lsb, nil
	}

	out, err := exec.Command("lsb_release", "-a").Output()
	if err != nil {
		return string(out), err
	}

	lsb = string(out)

	globalCache.Set(cacheKey, lsb)

	return lsb, nil
}

func getHostId() (string, error) {
	// XXX : hostid will not be reused, no need to cache the full output
	out, err := exec.Command("hostid").Output()
	if err != nil {
		return string(out), err
	}

	return string(out), nil
}

func getFileSystems() (string, error) {
	buff, err := ioutil.ReadFile("/proc/filesystems")
	if err != nil {
		return "", nil
	}

	return string(buff), nil
}

func getCpuInfos() (string, error) {
	buff, err := ioutil.ReadFile("/proc/cpuinfo")
	if err != nil {
		return "", err
	}

	return string(buff), err
}

// ----

type LibSysInfoErr struct {
	Msg string
}

func (e LibSysInfoErr) Error() string {
	return e.Msg
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"strings"
	"time"
	"unicode"

	"github.com/pmezard/adblock/adblock"
)

var (
	listen = flag.String("listen", "localhost:1080", "listen on address")
	logp   = flag.Bool("log", false, "enable logging")
)

type FilteringHandler struct {
	Matcher adblock.Matcher
	Rules   []string
	Jar     *cookiejar.Jar
}

func logRequest(r *http.Request) {
	log.Printf("%s %s %s %s\n", r.Proto, r.Method, r.URL, r.Host)
	buf := &bytes.Buffer{}
	r.Header.Write(buf)
	log.Println(string(buf.Bytes()))
}

func getReferrerDomain(r *http.Request) string {
	ref := r.Header.Get("Referer")
	if len(ref) > 0 {
		u, err := url.Parse(ref)
		if err == nil {
			return u.Host
		}
	}
	return ""
}

func (h *FilteringHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	if *logp {
		logRequest(r)
	}

	client := &http.Client{Jar: h.Jar}
	r.RequestURI = ""
	if len(r.URL.Scheme) > 0 {
		r.URL.Scheme = strings.Map(unicode.ToLower, r.URL.Scheme)
	} else {
		r.URL.Scheme = "http"
	}
	if len(r.URL.Host) == 0 {
		r.URL.Host = r.Host
	}

	rq := &adblock.Request{
		URL:          r.URL.String(),
		Domain:       r.URL.Host,
		OriginDomain: getReferrerDomain(r),
	}
	start := time.Now()
	matched, id := h.Matcher(rq)
	end := time.Now()
	duration := end.Sub(start) / time.Millisecond
	if matched {
		rule := h.Rules[id]
		log.Printf("rejected in %dms: %s\n", duration, r.URL.String())
		log.Printf("  by %s\n", rule)
		w.WriteHeader(404)
		return
	}

	if r.Method == "HEAD" {
		r.Header.Del("Accept-Encoding")
	}
	r.Close = true

	resp, err := client.Do(r)
	if err != nil && err != io.EOF {
		log.Printf("error: %s\n", err)
		if !*logp {
			logRequest(r)
		}
		return
	}
	duration2 := time.Duration(0)
	mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
	if err == nil && len(mediaType) > 0 {
		rq.ContentType = mediaType
		// Second level filtering, based on returned content
		start := time.Now()
		matched, id := h.Matcher(rq)
		end := time.Now()
		duration2 = end.Sub(start) / time.Millisecond
		if matched {
			rule := h.Rules[id]
			log.Printf("rejected in %d/%dms: %s\n", duration, duration2, r.URL.String())
			log.Printf("  by %s\n", rule)
			w.WriteHeader(404)
			return
		}
	}
	log.Printf("accepted in %d/%dms: %s\n", duration, duration2, r.URL.String())

	headers := w.Header()
	for k, v := range resp.Header {
		headers[k] = v
	}
	w.WriteHeader(resp.StatusCode)
	_, err = io.Copy(w, resp.Body)
	resp.Body.Close()
}

func loadBlackList(path string, matcher *adblock.RuleMatcher,
	rules []string) ([]string, int, error) {

	fp, err := os.Open(path)
	if err != nil {
		return nil, 0, err
	}
	defer fp.Close()

	read := 0
	scanner := bufio.NewScanner(fp)
	for scanner.Scan() {
		s := scanner.Text()
		rule, err := adblock.ParseRule(s)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: could not parse rule:\n  %s\n  %s\n",
				scanner.Text(), err)
			continue
		}
		if rule == nil {
			continue
		}
		err = matcher.AddRule(rule, len(rules))
		read += 1
		if err == nil {
			rules = append(rules, s)
		}
	}
	return rules, read, scanner.Err()
}

func loadBlackLists(paths []string) (adblock.Matcher, []string, error) {
	log.Printf("reading black lists\n")
	matcher := adblock.NewMatcher()
	read := 0
	rules := []string{}
	for _, path := range paths {
		updated, r, err := loadBlackList(path, matcher, rules)
		rules = updated
		if err != nil {
			return nil, nil, err
		}
		read += r
	}
	log.Printf("blacklists built: %d / %d added\n", len(rules), read)
	return matcher.Match, rules, nil
}

func runProxy() error {
	flag.Parse()
	matcher, rules, err := loadBlackLists(flag.Args())
	if err != nil {
		return err
	}
	jar, err := cookiejar.New(nil)
	if err != nil {
		return err
	}
	h := &FilteringHandler{
		Matcher: matcher,
		Rules:   rules,
		Jar:     jar,
	}
	return http.ListenAndServe(*listen, h)
}

func main() {
	err := runProxy()
	if err != nil {
		log.Fatalf("error: %s\n", err)
	}
}
<commit_msg>adstop: ensure response body is closed after proxying<commit_after>package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"os"
	"strings"
	"time"
	"unicode"

	"github.com/pmezard/adblock/adblock"
)

var (
	listen = flag.String("listen", "localhost:1080", "listen on address")
	logp   = flag.Bool("log", false, "enable logging")
)

type FilteringHandler struct {
	Matcher adblock.Matcher
	Rules   []string
	Jar     *cookiejar.Jar
}

func logRequest(r *http.Request) {
	log.Printf("%s %s %s %s\n", r.Proto, r.Method, r.URL, r.Host)
	buf := &bytes.Buffer{}
	r.Header.Write(buf)
	log.Println(string(buf.Bytes()))
}

func getReferrerDomain(r *http.Request) string {
	ref := r.Header.Get("Referer")
	if len(ref) > 0 {
		u, err := url.Parse(ref)
		if err == nil {
			return u.Host
		}
	}
	return ""
}

func (h *FilteringHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	if *logp {
		logRequest(r)
	}

	client := &http.Client{Jar: h.Jar}
	r.RequestURI = ""
	if len(r.URL.Scheme) > 0 {
		r.URL.Scheme = strings.Map(unicode.ToLower, r.URL.Scheme)
	} else {
		r.URL.Scheme = "http"
	}
	if len(r.URL.Host) == 0 {
		r.URL.Host = r.Host
	}

	rq := &adblock.Request{
		URL:          r.URL.String(),
		Domain:       r.URL.Host,
		OriginDomain: getReferrerDomain(r),
	}
	start := time.Now()
	matched, id := h.Matcher(rq)
	end := time.Now()
	duration := end.Sub(start) / time.Millisecond
	if matched {
		rule := h.Rules[id]
		log.Printf("rejected in %dms: %s\n", duration, r.URL.String())
		log.Printf("  by %s\n", rule)
		w.WriteHeader(404)
		return
	}

	if r.Method == "HEAD" {
		r.Header.Del("Accept-Encoding")
	}
	r.Close = true

	resp, err := client.Do(r)
	if resp.Body != nil {
		defer resp.Body.Close()
	}
	if err != nil && err != io.EOF {
		log.Printf("error: %s\n", err)
		if !*logp {
			logRequest(r)
		}
		return
	}
	duration2 := time.Duration(0)
	mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
	if err == nil && len(mediaType) > 0 {
		rq.ContentType = mediaType
		// Second level filtering, based on returned content
		start := time.Now()
		matched, id := h.Matcher(rq)
		end := time.Now()
		duration2 = end.Sub(start) / time.Millisecond
		if matched {
			rule := h.Rules[id]
			log.Printf("rejected in %d/%dms: %s\n", duration, duration2, r.URL.String())
			log.Printf("  by %s\n", rule)
			w.WriteHeader(404)
			return
		}
	}
	log.Printf("accepted in %d/%dms: %s\n", duration, duration2, r.URL.String())

	headers := w.Header()
	for k, v := range resp.Header {
		headers[k] = v
	}
	w.WriteHeader(resp.StatusCode)
	_, err = io.Copy(w, resp.Body)
}

func loadBlackList(path string, matcher *adblock.RuleMatcher,
	rules []string) ([]string, int, error) {

	fp, err := os.Open(path)
	if err != nil {
		return nil, 0, err
	}
	defer fp.Close()

	read := 0
	scanner := bufio.NewScanner(fp)
	for scanner.Scan() {
		s := scanner.Text()
		rule, err := adblock.ParseRule(s)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: could not parse rule:\n  %s\n  %s\n",
				scanner.Text(), err)
			continue
		}
		if rule == nil {
			continue
		}
		err = matcher.AddRule(rule, len(rules))
		read += 1
		if err == nil {
			rules = append(rules, s)
		}
	}
	return rules, read, scanner.Err()
}

func loadBlackLists(paths []string) (adblock.Matcher, []string, error) {
	log.Printf("reading black lists\n")
	matcher := adblock.NewMatcher()
	read := 0
	rules := []string{}
	for _, path := range paths {
		updated, r, err := loadBlackList(path, matcher, rules)
		rules = updated
		if err != nil {
			return nil, nil, err
		}
		read += r
	}
	log.Printf("blacklists built: %d / %d added\n", len(rules), read)
	return matcher.Match, rules, nil
}

func runProxy() error {
	flag.Parse()
	matcher, rules, err := loadBlackLists(flag.Args())
	if err != nil {
		return err
	}
	jar, err := cookiejar.New(nil)
	if err != nil {
		return err
	}
	h := &FilteringHandler{
		Matcher: matcher,
		Rules:   rules,
		Jar:     jar,
	}
	return http.ListenAndServe(*listen, h)
}

func main() {
	err := runProxy()
	if err != nil {
		log.Fatalf("error: %s\n", err)
	}
}
<|endoftext|>


<commit_before>package agent

// You can overridden buildVersion at compile time by using:
//
//  go run -ldflags "-X github.com/buildkite/agent/agent.buildVersion abc" *.go --version
//
// On CI, the binaries are always build with the buildVersion variable set.

var baseVersion string = "2.1.4"
var buildVersion string = ""

func Version() string {
	return baseVersion
}

func BuildVersion() string {
	if buildVersion != "" {
		return buildVersion
	} else {
		return "x"
	}
}
<commit_msg>Revert "Bumped to 2.1.4"<commit_after>package agent

// You can overridden buildVersion at compile time by using:
//
//  go run -ldflags "-X github.com/buildkite/agent/agent.buildVersion abc" *.go --version
//
// On CI, the binaries are always build with the buildVersion variable set.

var baseVersion string = "2.1.3"
var buildVersion string = ""

func Version() string {
	return baseVersion
}

func BuildVersion() string {
	if buildVersion != "" {
		return buildVersion
	} else {
		return "x"
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"github.com/dim13/unifi"
	"log"
	"time"
)

type roaming struct {
	Name    string
	Ip      string
	Ap      string
	Channel int
	Essid   string
}

type roamMap map[string]roaming

var stamap roamMap

var (
	user = flag.String("user", "admin", "User")
	pass = flag.String("pass", "unifi", "Password")
	url = flag.String("url", "unifi", "URL")
	delay = flag.Int("delay", 5, "delay")
)

func main() {
	flag.Parse()
	u := unifi.Login(*user, *pass, *url)
	defer u.Logout()

	apsmap := u.ApsMap()

	for {
		newmap := make(roamMap)
		for _, s := range u.Sta() {
			newmap[s.Mac] = roaming{
				Name:    s.Name(),
				Ip:      s.Ip,
				Ap:      apsmap[s.Ap_mac].Name,
				Channel: s.Channel,
				Essid:   s.Essid,
			}
		}
		for k, v := range newmap {
			if z, ok := stamap[k]; !ok {
				log.Printf("%s appears on %s/%d (%s/%s)\n",
					v.Name, v.Ap, v.Channel, v.Essid, v.Ip)
			} else if z != v {
				log.Printf("%s roams %s/%d (%s/%s) -> %s/%d (%s/%s)\n",
					v.Name,
					z.Ap, z.Channel, z.Essid, z.Ip,
					v.Ap, v.Channel, v.Essid, v.Ip)
			}
			delete(stamap, k)
		}
		for _, v := range stamap {
			log.Printf("%s vanishes from %s/%d (%s/%s)\n",
				v.Name, v.Ap, v.Channel, v.Essid, v.Ip)
		}
		stamap = newmap
		time.Sleep(time.Duration(*delay) * time.Second)
	}
}
<commit_msg>gofmt<commit_after>package main

import (
	"flag"
	"github.com/dim13/unifi"
	"log"
	"time"
)

type roaming struct {
	Name    string
	Ip      string
	Ap      string
	Channel int
	Essid   string
}

type roamMap map[string]roaming

var stamap roamMap

var (
	user  = flag.String("user", "admin", "User")
	pass  = flag.String("pass", "unifi", "Password")
	url   = flag.String("url", "unifi", "URL")
	delay = flag.Int("delay", 5, "delay")
)

func main() {
	flag.Parse()
	u := unifi.Login(*user, *pass, *url)
	defer u.Logout()

	apsmap := u.ApsMap()

	for {
		newmap := make(roamMap)
		for _, s := range u.Sta() {
			newmap[s.Mac] = roaming{
				Name:    s.Name(),
				Ip:      s.Ip,
				Ap:      apsmap[s.Ap_mac].Name,
				Channel: s.Channel,
				Essid:   s.Essid,
			}
		}
		for k, v := range newmap {
			if z, ok := stamap[k]; !ok {
				log.Printf("%s appears on %s/%d (%s/%s)\n",
					v.Name, v.Ap, v.Channel, v.Essid, v.Ip)
			} else if z != v {
				log.Printf("%s roams %s/%d (%s/%s) -> %s/%d (%s/%s)\n",
					v.Name,
					z.Ap, z.Channel, z.Essid, z.Ip,
					v.Ap, v.Channel, v.Essid, v.Ip)
			}
			delete(stamap, k)
		}
		for _, v := range stamap {
			log.Printf("%s vanishes from %s/%d (%s/%s)\n",
				v.Name, v.Ap, v.Channel, v.Essid, v.Ip)
		}
		stamap = newmap
		time.Sleep(time.Duration(*delay) * time.Second)
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package log

import (
	"encoding/json"
	"errors"
	"fmt"
	"log/syslog"
	"os"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
)

// singleton defines the object of a Singleton pattern
type singleton struct {
	once sync.Once
	log  *AuditLogger
}

// _Singleton is the single AuditLogger entity in memory
var _Singleton singleton

// The constant used to identify audit-specific messages
const auditTag = "[AUDIT]"

// Constant used to indicate an emergency exit to the executor
const emergencyReturnValue = 13

// exitFunction closes the running system
type exitFunction func()

// Default to calling os.Exit()
func defaultEmergencyExit() {
	os.Exit(emergencyReturnValue)
}

// AuditLogger is a System Logger with additional audit-specific methods.
// In addition to all the standard syslog.Writer methods from
// http://golang.org/pkg/log/syslog/#Writer, you can also call
//   auditLogger.Audit(msg string)
// to send a message as an audit event.
type AuditLogger struct {
	*syslog.Writer
	Stats        statsd.Statter
	exitFunction exitFunction
}

// Dial establishes a connection to the log daemon by passing through
// the parameters to the syslog.Dial method.
// See http://golang.org/pkg/log/syslog/#Dial
func Dial(network, raddr string, tag string, stats statsd.Statter) (*AuditLogger, error) {
	syslogger, err := syslog.Dial(network, raddr, syslog.LOG_INFO|syslog.LOG_LOCAL0, tag)
	if err != nil {
		return nil, err
	}
	return NewAuditLogger(syslogger, stats)
}

// NewAuditLogger constructs an Audit Logger that decorates a normal
// System Logger. All methods in log/syslog continue to work.
func NewAuditLogger(log *syslog.Writer, stats statsd.Statter) (*AuditLogger, error) {
	if log == nil {
		return nil, errors.New("Attempted to use a nil System Logger.")
	}
	audit := &AuditLogger{
		log,
		stats,
		defaultEmergencyExit,
	}
	return audit, nil
}

// initializeAuditLogger should only be used in unit tests. Failures in this
// method are unlikely as the defaults are safe, and they are also
// of minimal consequence during unit testing -- logs get printed to stdout
// even if syslog is missing.
func initializeAuditLogger() {
	stats, _ := statsd.NewNoopClient(nil)
	audit, _ := Dial("", "", "default", stats)
	audit.Notice("Using default logging configuration.")

	SetAuditLogger(audit)
}

// SetAuditLogger configures the singleton audit logger. This method
// must only be called once, and before calling GetAuditLogger the
// first time.
func SetAuditLogger(logger *AuditLogger) (err error) {
	if _Singleton.log != nil {
		err = errors.New("You may not call SetAuditLogger after it has already been implicitly or explicitly set.")
		_Singleton.log.WarningErr(err)
	} else {
		_Singleton.log = logger
	}
	return
}

// GetAuditLogger obtains the singleton audit logger. If SetAuditLogger
// has not been called first, this method initializes with basic defaults.
// The basic defaults cannot error, and subequent access to an already-set
// AuditLogger also cannot error, so this method is error-safe.
func GetAuditLogger() *AuditLogger {
	_Singleton.once.Do(func() {
		if _Singleton.log == nil {
			initializeAuditLogger()
		}
	})

	return _Singleton.log
}

// Log the provided message at the appropriate level, writing to
// both stdout and the Logger, as well as informing statsd.
func (log *AuditLogger) logAtLevel(level, msg string) (err error) {
	fmt.Printf("%s %s\n", time.Now().Format("2006/01/02 15:04:05"), msg)
	log.Stats.Inc(level, 1, 1.0)

	switch level {
	case "Logging.Alert":
		err = log.Writer.Alert(msg)
	case "Logging.Crit":
		err = log.Writer.Crit(msg)
	case "Logging.Debug":
		err = log.Writer.Debug(msg)
	case "Logging.Emerg":
		err = log.Writer.Emerg(msg)
	case "Logging.Err":
		err = log.Writer.Err(msg)
	case "Logging.Info":
		err = log.Writer.Info(msg)
	case "Logging.Warning":
		err = log.Writer.Warning(msg)
	case "Logging.Notice":
		err = log.Writer.Notice(msg)
	default:
		err = fmt.Errorf("Unknown logging level: %s", level)
	}
	return
}

// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) auditAtLevel(level, msg string) (err error) {
	// Submit a separate counter that marks an Audit event
	log.Stats.Inc("Logging.Audit", 1, 1.0)

	text := fmt.Sprintf("%s %s", auditTag, msg)
	return log.logAtLevel(level, text)
}

// Return short format caller info for panic events, skipping to before the
// panic handler.
func caller(level int) string {
	_, file, line, _ := runtime.Caller(level)
	splits := strings.Split(file, "/")
	filename := splits[len(splits)-1]
	return fmt.Sprintf("%s:%d:", filename, line)
}

// AuditPanic catches panicking executables. This method should be added
// in a defer statement as early as possible
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) AuditPanic() {
	if err := recover(); err != nil {
		buf := make([]byte, 8192)
		log.Audit(fmt.Sprintf("Panic caused by err: %v", err))

		runtime.Stack(buf, false)
		log.Audit(fmt.Sprintf("Stack Trace (Current frame) %s", buf))

		runtime.Stack(buf, true)
		log.Warning(fmt.Sprintf("Stack Trace (All frames): %s", buf))
	}
}

// WarningErr formats an error for the Warn level.
func (log *AuditLogger) WarningErr(msg error) (err error) {
	return log.logAtLevel("Logging.Warning", msg.Error())
}

// Alert level messages pass through normally.
func (log *AuditLogger) Alert(msg string) (err error) {
	return log.logAtLevel("Logging.Alert", msg)
}

// Crit level messages are automatically marked for audit
func (log *AuditLogger) Crit(msg string) (err error) {
	return log.auditAtLevel("Logging.Crit", msg)
}

// Debug level messages pass through normally.
func (log *AuditLogger) Debug(msg string) (err error) {
	return log.logAtLevel("Logging.Debug", msg)
}

// Emerg level messages are automatically marked for audit
func (log *AuditLogger) Emerg(msg string) (err error) {
	return log.auditAtLevel("Logging.Emerg", msg)
}

// Err level messages are automatically marked for audit
func (log *AuditLogger) Err(msg string) (err error) {
	return log.auditAtLevel("Logging.Err", msg)
}

// Info level messages pass through normally.
func (log *AuditLogger) Info(msg string) (err error) {
	return log.logAtLevel("Logging.Info", msg)
}

// Warning level messages pass through normally.
func (log *AuditLogger) Warning(msg string) (err error) {
	return log.logAtLevel("Logging.Warning", msg)
}

// Notice level messages pass through normally.
func (log *AuditLogger) Notice(msg string) (err error) {
	return log.logAtLevel("Logging.Notice", msg)
}

// Audit sends a NOTICE-severity message that is prefixed with the
// audit tag, for special handling at the upstream system logger.
func (log *AuditLogger) Audit(msg string) (err error) {
	return log.auditAtLevel("Logging.Notice", msg)
}

func (log *AuditLogger) formatObject(obj interface{}) (string, error) {
	jsonObj, err := json.Marshal(obj)
	if err != nil {
		// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
		log.auditAtLevel("Logging.Err", fmt.Sprintf("Object could not be serialized to JSON. Raw: %+v", obj))
		return "", err
	}

	return fmt.Sprintf("JSON=%s", jsonObj), nil
}

// AuditObject sends a NOTICE-severity JSON-serialized object message that is prefixed
// with the audit tag, for special handling at the upstream system logger.
func (log *AuditLogger) AuditObject(msg string, obj interface{}) (err error) {
	jsonLogEvent, logErr := log.formatObject(obj)
	if logErr != nil {
		return logErr
	}

	return log.auditAtLevel("Logging.Notice", fmt.Sprintf("%s %s", msg, jsonLogEvent))
}

// Object sends a INFO-severity JSON-serialized object message.
func (log *AuditLogger) InfoObject(msg string, obj interface{}) (err error) {
	jsonLogEvent, logErr := log.formatObject(obj)
	if logErr != nil {
		return logErr
	}

	return log.logAtLevel("Logging.Info", fmt.Sprintf("%s %s", msg, jsonLogEvent))
}

// AuditErr can format an error for auditing; it does so at ERR level.
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) AuditErr(msg error) (err error) {
	return log.auditAtLevel("Logging.Err", msg.Error())
}

// SetEmergencyExitFunc changes the systems' behavior on an emergency exit.
func (log *AuditLogger) SetEmergencyExitFunc(exit exitFunction) {
	log.exitFunction = exit
}

// EmergencyExit triggers an immediate Boulder shutdown in the event of serious
// errors. This function will provide the necessary housekeeping.
// Currently, make an emergency log entry and exit; the Activity Monitor
// should notice the Emerg level event and shut down all components.
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) EmergencyExit(msg string) {
	log.auditAtLevel("Logging.Emerg", msg)
	log.exitFunction()
}
<commit_msg>Remove duplication<commit_after>// Copyright 2015 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package log

import (
	"encoding/json"
	"errors"
	"fmt"
	"log/syslog"
	"os"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
)

// singleton defines the object of a Singleton pattern
type singleton struct {
	once sync.Once
	log  *AuditLogger
}

// _Singleton is the single AuditLogger entity in memory
var _Singleton singleton

// The constant used to identify audit-specific messages
const auditTag = "[AUDIT]"

// Constant used to indicate an emergency exit to the executor
const emergencyReturnValue = 13

// exitFunction closes the running system
type exitFunction func()

// Default to calling os.Exit()
func defaultEmergencyExit() {
	os.Exit(emergencyReturnValue)
}

// AuditLogger is a System Logger with additional audit-specific methods.
// In addition to all the standard syslog.Writer methods from
// http://golang.org/pkg/log/syslog/#Writer, you can also call
//   auditLogger.Audit(msg string)
// to send a message as an audit event.
type AuditLogger struct {
	*syslog.Writer
	Stats        statsd.Statter
	exitFunction exitFunction
}

// Dial establishes a connection to the log daemon by passing through
// the parameters to the syslog.Dial method.
// See http://golang.org/pkg/log/syslog/#Dial
func Dial(network, raddr string, tag string, stats statsd.Statter) (*AuditLogger, error) {
	syslogger, err := syslog.Dial(network, raddr, syslog.LOG_INFO|syslog.LOG_LOCAL0, tag)
	if err != nil {
		return nil, err
	}
	return NewAuditLogger(syslogger, stats)
}

// NewAuditLogger constructs an Audit Logger that decorates a normal
// System Logger. All methods in log/syslog continue to work.
func NewAuditLogger(log *syslog.Writer, stats statsd.Statter) (*AuditLogger, error) {
	if log == nil {
		return nil, errors.New("Attempted to use a nil System Logger.")
	}
	audit := &AuditLogger{
		log,
		stats,
		defaultEmergencyExit,
	}
	return audit, nil
}

// initializeAuditLogger should only be used in unit tests. Failures in this
// method are unlikely as the defaults are safe, and they are also
// of minimal consequence during unit testing -- logs get printed to stdout
// even if syslog is missing.
func initializeAuditLogger() {
	stats, _ := statsd.NewNoopClient(nil)
	audit, _ := Dial("", "", "default", stats)
	audit.Notice("Using default logging configuration.")

	SetAuditLogger(audit)
}

// SetAuditLogger configures the singleton audit logger. This method
// must only be called once, and before calling GetAuditLogger the
// first time.
func SetAuditLogger(logger *AuditLogger) (err error) {
	if _Singleton.log != nil {
		err = errors.New("You may not call SetAuditLogger after it has already been implicitly or explicitly set.")
		_Singleton.log.WarningErr(err)
	} else {
		_Singleton.log = logger
	}
	return
}

// GetAuditLogger obtains the singleton audit logger. If SetAuditLogger
// has not been called first, this method initializes with basic defaults.
// The basic defaults cannot error, and subequent access to an already-set
// AuditLogger also cannot error, so this method is error-safe.
func GetAuditLogger() *AuditLogger {
	_Singleton.once.Do(func() {
		if _Singleton.log == nil {
			initializeAuditLogger()
		}
	})

	return _Singleton.log
}

// Log the provided message at the appropriate level, writing to
// both stdout and the Logger, as well as informing statsd.
func (log *AuditLogger) logAtLevel(level, msg string) (err error) {
	fmt.Printf("%s %s\n", time.Now().Format("2006/01/02 15:04:05"), msg)
	log.Stats.Inc(level, 1, 1.0)

	switch level {
	case "Logging.Alert":
		err = log.Writer.Alert(msg)
	case "Logging.Crit":
		err = log.Writer.Crit(msg)
	case "Logging.Debug":
		err = log.Writer.Debug(msg)
	case "Logging.Emerg":
		err = log.Writer.Emerg(msg)
	case "Logging.Err":
		err = log.Writer.Err(msg)
	case "Logging.Info":
		err = log.Writer.Info(msg)
	case "Logging.Warning":
		err = log.Writer.Warning(msg)
	case "Logging.Notice":
		err = log.Writer.Notice(msg)
	default:
		err = fmt.Errorf("Unknown logging level: %s", level)
	}
	return
}

// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) auditAtLevel(level, msg string) (err error) {
	// Submit a separate counter that marks an Audit event
	log.Stats.Inc("Logging.Audit", 1, 1.0)

	text := fmt.Sprintf("%s %s", auditTag, msg)
	return log.logAtLevel(level, text)
}

// Return short format caller info for panic events, skipping to before the
// panic handler.
func caller(level int) string {
	_, file, line, _ := runtime.Caller(level)
	splits := strings.Split(file, "/")
	filename := splits[len(splits)-1]
	return fmt.Sprintf("%s:%d:", filename, line)
}

// AuditPanic catches panicking executables. This method should be added
// in a defer statement as early as possible
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) AuditPanic() {
	if err := recover(); err != nil {
		buf := make([]byte, 8192)
		log.Audit(fmt.Sprintf("Panic caused by err: %v", err))

		runtime.Stack(buf, false)
		log.Audit(fmt.Sprintf("Stack Trace (Current frame) %s", buf))

		runtime.Stack(buf, true)
		log.Warning(fmt.Sprintf("Stack Trace (All frames): %s", buf))
	}
}

// WarningErr formats an error for the Warn level.
func (log *AuditLogger) WarningErr(msg error) (err error) {
	return log.logAtLevel("Logging.Warning", msg.Error())
}

// Alert level messages pass through normally.
func (log *AuditLogger) Alert(msg string) (err error) {
	return log.logAtLevel("Logging.Alert", msg)
}

// Crit level messages are automatically marked for audit
func (log *AuditLogger) Crit(msg string) (err error) {
	return log.auditAtLevel("Logging.Crit", msg)
}

// Debug level messages pass through normally.
func (log *AuditLogger) Debug(msg string) (err error) {
	return log.logAtLevel("Logging.Debug", msg)
}

// Emerg level messages are automatically marked for audit
func (log *AuditLogger) Emerg(msg string) (err error) {
	return log.auditAtLevel("Logging.Emerg", msg)
}

// Err level messages are automatically marked for audit
func (log *AuditLogger) Err(msg string) (err error) {
	return log.auditAtLevel("Logging.Err", msg)
}

// Info level messages pass through normally.
func (log *AuditLogger) Info(msg string) (err error) {
	return log.logAtLevel("Logging.Info", msg)
}

// Warning level messages pass through normally.
func (log *AuditLogger) Warning(msg string) (err error) {
	return log.logAtLevel("Logging.Warning", msg)
}

// Notice level messages pass through normally.
func (log *AuditLogger) Notice(msg string) (err error) {
	return log.logAtLevel("Logging.Notice", msg)
}

// Audit sends a NOTICE-severity message that is prefixed with the
// audit tag, for special handling at the upstream system logger.
func (log *AuditLogger) Audit(msg string) (err error) {
	return log.auditAtLevel("Logging.Notice", msg)
}

func (log *AuditLogger) formatObjectMessage(msg string, obj interface{}) (string, error) {
	jsonObj, err := json.Marshal(obj)
	if err != nil {
		// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
		log.auditAtLevel("Logging.Err", fmt.Sprintf("Object could not be serialized to JSON. Raw: %+v", obj))
		return "", err
	}

	return fmt.Sprintf("%s JSON=%s", msg, jsonObj), nil
}

// AuditObject sends a NOTICE-severity JSON-serialized object message that is prefixed
// with the audit tag, for special handling at the upstream system logger.
func (log *AuditLogger) AuditObject(msg string, obj interface{}) (err error) {
	formattedEvent, logErr := log.formatObjectMessage(msg, obj)
	if logErr != nil {
		return logErr
	}

	return log.auditAtLevel("Logging.Notice", formattedEvent)
}

// InfoObject sends a INFO-severity JSON-serialized object message.
func (log *AuditLogger) InfoObject(msg string, obj interface{}) (err error) {
	formattedEvent, logErr := log.formatObjectMessage(msg, obj)
	if logErr != nil {
		return logErr
	}

	return log.logAtLevel("Logging.Info", formattedEvent)
}

// AuditErr can format an error for auditing; it does so at ERR level.
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) AuditErr(msg error) (err error) {
	return log.auditAtLevel("Logging.Err", msg.Error())
}

// SetEmergencyExitFunc changes the systems' behavior on an emergency exit.
func (log *AuditLogger) SetEmergencyExitFunc(exit exitFunction) {
	log.exitFunction = exit
}

// EmergencyExit triggers an immediate Boulder shutdown in the event of serious
// errors. This function will provide the necessary housekeeping.
// Currently, make an emergency log entry and exit; the Activity Monitor
// should notice the Emerg level event and shut down all components.
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
func (log *AuditLogger) EmergencyExit(msg string) {
	log.auditAtLevel("Logging.Emerg", msg)
	log.exitFunction()
}
<|endoftext|>