Commit 59956903 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #46076 from liggitt/node-authorizer

Automatic merge from submit-queue Node authorizer This PR implements the authorization portion of https://github.com/kubernetes/community/blob/master/contributors/design-proposals/kubelet-authorizer.md and kubernetes/features#279: * Adds a new authorization mode (`Node`) that authorizes requests from nodes based on a graph of related pods,secrets,configmaps,pvcs, and pvs: * Watches pods, adds edges (secret -> pod, configmap -> pod, pvc -> pod, pod -> node) * Watches pvs, adds edges (secret -> pv, pv -> pvc) * When both Node and RBAC authorization modes are enabled, the default RBAC binding that grants the `system:node` role to the `system:nodes` group is not automatically created. * Tightens the `NodeRestriction` admission plugin to require identifiable nodes for requests from users in the `system:nodes` group. This authorization mode is intended to be used in combination with the `NodeRestriction` admission plugin, which limits the pods and nodes a node may modify. To enable in combination with RBAC authorization and the NodeRestriction admission plugin: * start the API server with `--authorization-mode=Node,RBAC --admission-control=...,NodeRestriction,...` * start kubelets with TLS boostrapping or with client credentials that place them in the `system:nodes` group with a username of `system:node:<nodeName>` ```release-note kube-apiserver: a new authorization mode (`--authorization-mode=Node`) authorizes nodes to access secrets, configmaps, persistent volume claims and persistent volumes related to their pods. * Nodes must use client credentials that place them in the `system:nodes` group with a username of `system:node:<nodeName>` in order to be authorized by the node authorizer (the credentials obtained by the kubelet via TLS bootstrapping satisfy these requirements) * When used in combination with the `RBAC` authorization mode (`--authorization-mode=Node,RBAC`), the `system:node` role is no longer automatically granted to the `system:nodes` group. ``` ```release-note RBAC: the automatic binding of the `system:node` role to the `system:nodes` group is deprecated and will not be created in future releases. It is recommended that nodes be authorized using the new `Node` authorization mode instead. Installations that wish to continue giving all members of the `system:nodes` group the `system:node` role (which grants broad read access, including all secrets and configmaps) must create an installation-specific ClusterRoleBinding. ``` Follow-up: - [ ] enable e2e CI environment with admission and authorizer enabled (blocked by kubelet TLS bootstrapping enablement in https://github.com/kubernetes/kubernetes/pull/40760) - [ ] optionally enable this authorizer and admission plugin in kubeadm - [ ] optionally enable this authorizer and admission plugin in kube-up
parents 1f213765 fc8e915a
......@@ -2740,6 +2740,10 @@
"Rev": "f51c12702a4d776e4c1fa9b0fabab841babae631"
},
{
"ImportPath": "golang.org/x/tools/container/intsets",
"Rev": "2382e3994d48b1d22acc2c86bcad0a2aff028e32"
},
{
"ImportPath": "google.golang.org/api/cloudmonitoring/v2beta2",
"Rev": "e3824ed33c72bf7e81da0286772c34b987520914"
},
......
......@@ -83173,6 +83173,41 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================================
= vendor/golang.org/x/tools/container/intsets licensed under: =
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
= vendor/golang.org/x/tools/LICENSE 5d4950ecb7b26d2c5e4e7b4e0dd74707 -
================================================================================
================================================================================
= vendor/google.golang.org/api/cloudmonitoring/v2beta2 licensed under: =
Copyright (c) 2011 Google Inc. All rights reserved.
......@@ -59,6 +59,7 @@ pkg/api/errors
pkg/api/events
pkg/api/install
pkg/api/meta
pkg/api/persistentvolume
pkg/api/pod
pkg/api/resource
pkg/api/service
......
......@@ -76,6 +76,7 @@ ENABLE_APISERVER_BASIC_AUDIT=${ENABLE_APISERVER_BASIC_AUDIT:-false}
# RBAC Mode options
ALLOW_ANY_TOKEN=${ALLOW_ANY_TOKEN:-false}
ENABLE_RBAC=${ENABLE_RBAC:-false}
AUTHORIZATION_MODE=${AUTHORIZATION_MODE:-""}
KUBECONFIG_TOKEN=${KUBECONFIG_TOKEN:-""}
AUTH_ARGS=${AUTH_ARGS:-""}
......@@ -201,6 +202,8 @@ KUBELET_HOST=${KUBELET_HOST:-"127.0.0.1"}
API_CORS_ALLOWED_ORIGINS=${API_CORS_ALLOWED_ORIGINS:-/127.0.0.1(:[0-9]+)?$,/localhost(:[0-9]+)?$}
KUBELET_PORT=${KUBELET_PORT:-10250}
LOG_LEVEL=${LOG_LEVEL:-3}
# Use to increase verbosity on particular files, e.g. LOG_SPEC=token_controller*=5,other_controller*=4
LOG_SPEC=${LOG_SPEC:-""}
LOG_DIR=${LOG_DIR:-"/tmp"}
CONTAINER_RUNTIME=${CONTAINER_RUNTIME:-"docker"}
CONTAINER_RUNTIME_ENDPOINT=${CONTAINER_RUNTIME_ENDPOINT:-""}
......@@ -435,6 +438,12 @@ function start_apiserver {
if [[ "${ENABLE_RBAC}" = true ]]; then
authorizer_arg="--authorization-mode=RBAC "
fi
if [[ -n "${AUTHORIZATION_MODE}" ]]; then
if [[ "${ENABLE_RBAC}" = true ]]; then
warning "AUTHORIZATION_MODE=$AUTHORIZATION_MODE overrode ENABLE_RBAC=true"
fi
authorizer_arg="--authorization-mode=${AUTHORIZATION_MODE} "
fi
priv_arg=""
if [[ -n "${ALLOW_PRIVILEGED}" ]]; then
priv_arg="--allow-privileged "
......@@ -487,6 +496,7 @@ function start_apiserver {
${CONTROLPLANE_SUDO} "${GO_OUT}/hyperkube" apiserver ${swagger_arg} ${audit_arg} ${anytoken_arg} ${authorizer_arg} ${priv_arg} ${runtime_config}\
${advertise_address} \
--v=${LOG_LEVEL} \
--vmodule="${LOG_SPEC}" \
--cert-dir="${CERT_DIR}" \
--client-ca-file="${CERT_DIR}/client-ca.crt" \
--service-account-key-file="${SERVICE_ACCOUNT_KEY}" \
......@@ -894,7 +904,7 @@ if [[ "${START_MODE}" != "nokubelet" ]]; then
esac
fi
if [[ -n "${PSP_ADMISSION}" && "${ENABLE_RBAC}" = true ]]; then
if [[ -n "${PSP_ADMISSION}" && ("${ENABLE_RBAC}" = true || "${AUTHORIZATION_MODE}" = *RBAC* ) ]]; then
create_psp_policy
fi
......
......@@ -107,6 +107,7 @@ filegroup(
"//pkg/api/helper:all-srcs",
"//pkg/api/install:all-srcs",
"//pkg/api/meta:all-srcs",
"//pkg/api/persistentvolume:all-srcs",
"//pkg/api/pod:all-srcs",
"//pkg/api/ref:all-srcs",
"//pkg/api/resource:all-srcs",
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["util.go"],
tags = ["automanaged"],
deps = ["//pkg/api:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["util_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
reviewers:
- smarterclayton
- kargakis
- david-mcmahon
/*
Copyright 2017 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 persistentvolume
import (
"k8s.io/kubernetes/pkg/api"
)
// VisitPVSecretNames invokes the visitor function with the name of every secret
// referenced by the PV spec. If visitor returns false, visiting is short-circuited.
// Returns true if visiting completed, false if visiting was short-circuited.
func VisitPVSecretNames(pv *api.PersistentVolume, visitor func(string) bool) bool {
source := &pv.Spec.PersistentVolumeSource
switch {
case source.AzureFile != nil:
if len(source.AzureFile.SecretName) > 0 && !visitor(source.AzureFile.SecretName) {
return false
}
case source.CephFS != nil:
if source.CephFS.SecretRef != nil && !visitor(source.CephFS.SecretRef.Name) {
return false
}
case source.FlexVolume != nil:
if source.FlexVolume.SecretRef != nil && !visitor(source.FlexVolume.SecretRef.Name) {
return false
}
case source.RBD != nil:
if source.RBD.SecretRef != nil && !visitor(source.RBD.SecretRef.Name) {
return false
}
case source.ScaleIO != nil:
if source.ScaleIO.SecretRef != nil && !visitor(source.ScaleIO.SecretRef.Name) {
return false
}
case source.ISCSI != nil:
if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) {
return false
}
}
return true
}
/*
Copyright 2017 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 persistentvolume
import (
"reflect"
"testing"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/api"
)
func TestPVSecrets(t *testing.T) {
// Stub containing all possible secret references in a PV.
// The names of the referenced secrets match struct paths detected by reflection.
pvs := []*api.PersistentVolume{
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{
AzureFile: &api.AzureFileVolumeSource{
SecretName: "Spec.PersistentVolumeSource.AzureFile.SecretName"}}}},
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{
CephFS: &api.CephFSVolumeSource{
SecretRef: &api.LocalObjectReference{
Name: "Spec.PersistentVolumeSource.CephFS.SecretRef"}}}}},
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{
FlexVolume: &api.FlexVolumeSource{
SecretRef: &api.LocalObjectReference{
Name: "Spec.PersistentVolumeSource.FlexVolume.SecretRef"}}}}},
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{
RBD: &api.RBDVolumeSource{
SecretRef: &api.LocalObjectReference{
Name: "Spec.PersistentVolumeSource.RBD.SecretRef"}}}}},
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{
ScaleIO: &api.ScaleIOVolumeSource{
SecretRef: &api.LocalObjectReference{
Name: "Spec.PersistentVolumeSource.ScaleIO.SecretRef"}}}}},
{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{
ISCSI: &api.ISCSIVolumeSource{
SecretRef: &api.LocalObjectReference{
Name: "Spec.PersistentVolumeSource.ISCSI.SecretRef"}}}}},
}
extractedNames := sets.NewString()
for _, pv := range pvs {
VisitPVSecretNames(pv, func(name string) bool {
extractedNames.Insert(name)
return true
})
}
// excludedSecretPaths holds struct paths to fields with "secret" in the name that are not actually references to secret API objects
excludedSecretPaths := sets.NewString(
"Spec.PersistentVolumeSource.CephFS.SecretFile",
)
// expectedSecretPaths holds struct paths to fields with "secret" in the name that are references to secret API objects.
// every path here should be represented as an example in the PV stub above, with the secret name set to the path.
expectedSecretPaths := sets.NewString(
"Spec.PersistentVolumeSource.AzureFile.SecretName",
"Spec.PersistentVolumeSource.CephFS.SecretRef",
"Spec.PersistentVolumeSource.FlexVolume.SecretRef",
"Spec.PersistentVolumeSource.RBD.SecretRef",
"Spec.PersistentVolumeSource.ScaleIO.SecretRef",
"Spec.PersistentVolumeSource.ISCSI.SecretRef",
)
secretPaths := collectSecretPaths(t, nil, "", reflect.TypeOf(&api.PersistentVolume{}))
secretPaths = secretPaths.Difference(excludedSecretPaths)
if missingPaths := expectedSecretPaths.Difference(secretPaths); len(missingPaths) > 0 {
t.Logf("Missing expected secret paths:\n%s", strings.Join(missingPaths.List(), "\n"))
t.Error("Missing expected secret paths. Verify VisitPVSecretNames() is correctly finding the missing paths, then correct expectedSecretPaths")
}
if extraPaths := secretPaths.Difference(expectedSecretPaths); len(extraPaths) > 0 {
t.Logf("Extra secret paths:\n%s", strings.Join(extraPaths.List(), "\n"))
t.Error("Extra fields with 'secret' in the name found. Verify VisitPVSecretNames() is including these fields if appropriate, then correct expectedSecretPaths")
}
if missingNames := expectedSecretPaths.Difference(extractedNames); len(missingNames) > 0 {
t.Logf("Missing expected secret names:\n%s", strings.Join(missingNames.List(), "\n"))
t.Error("Missing expected secret names. Verify the PV stub above includes these references, then verify VisitPVSecretNames() is correctly finding the missing names")
}
if extraNames := extractedNames.Difference(expectedSecretPaths); len(extraNames) > 0 {
t.Logf("Extra secret names:\n%s", strings.Join(extraNames.List(), "\n"))
t.Error("Extra secret names extracted. Verify VisitPVSecretNames() is correctly extracting secret names")
}
}
// collectSecretPaths traverses the object, computing all the struct paths that lead to fields with "secret" in the name.
func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect.Type) sets.String {
secretPaths := sets.NewString()
if tp.Kind() == reflect.Ptr {
secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...)
return secretPaths
}
if strings.Contains(strings.ToLower(name), "secret") {
secretPaths.Insert(path.String())
}
switch tp.Kind() {
case reflect.Ptr:
secretPaths.Insert(collectSecretPaths(t, path, name, tp.Elem()).List()...)
case reflect.Struct:
for i := 0; i < tp.NumField(); i++ {
field := tp.Field(i)
secretPaths.Insert(collectSecretPaths(t, path.Child(field.Name), field.Name, field.Type).List()...)
}
case reflect.Interface:
t.Errorf("cannot find secret fields in interface{} field %s", path.String())
case reflect.Map:
secretPaths.Insert(collectSecretPaths(t, path.Key("*"), "", tp.Elem()).List()...)
case reflect.Slice:
secretPaths.Insert(collectSecretPaths(t, path.Key("*"), "", tp.Elem()).List()...)
default:
// all primitive types
}
return secretPaths
}
......@@ -26,10 +26,13 @@ go_library(
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/auth/authorizer/abac:go_default_library",
"//pkg/auth/nodeidentifier:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/rbac/internalversion:go_default_library",
"//pkg/kubeapiserver/authorizer/modes:go_default_library",
"//plugin/pkg/auth/authorizer/node:go_default_library",
"//plugin/pkg/auth/authorizer/rbac:go_default_library",
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
......
......@@ -28,10 +28,13 @@ import (
"k8s.io/apiserver/plugin/pkg/authorizer/webhook"
rbacapi "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/auth/authorizer/abac"
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
rbaclisters "k8s.io/kubernetes/pkg/client/listers/rbac/internalversion"
"k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/node"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
)
type AuthorizationConfig struct {
......@@ -107,6 +110,19 @@ func (config AuthorizationConfig) New() (authorizer.Authorizer, error) {
}
// Keep cases in sync with constant list above.
switch authorizationMode {
case modes.ModeNode:
graph := node.NewGraph()
node.AddGraphEventHandlers(
graph,
config.InformerFactory.Core().InternalVersion().Pods(),
config.InformerFactory.Core().InternalVersion().PersistentVolumes(),
)
nodeAuthorizer := node.NewAuthorizer(graph, nodeidentifier.NewDefaultNodeIdentifier(), bootstrappolicy.NodeRules())
authorizers = append(authorizers, nodeAuthorizer)
// Don't bind system:nodes to the system:node role
bootstrappolicy.AddClusterRoleBindingFilter(bootstrappolicy.OmitNodesGroupBinding)
case modes.ModeAlwaysAllow:
authorizers = append(authorizers, authorizerfactory.NewAlwaysAllowAuthorizer())
case modes.ModeAlwaysDeny:
......
......@@ -22,9 +22,10 @@ const (
ModeABAC string = "ABAC"
ModeWebhook string = "Webhook"
ModeRBAC string = "RBAC"
ModeNode string = "Node"
)
var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC, ModeWebhook, ModeRBAC}
var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC, ModeWebhook, ModeRBAC, ModeNode}
// IsValidAuthorizationMode returns true if the given authorization mode is a valid one for the apiserver
func IsValidAuthorizationMode(authzMode string) bool {
......
......@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package node
package noderestriction
import (
"fmt"
......@@ -37,24 +37,22 @@ const (
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
return NewPlugin(nodeidentifier.NewDefaultNodeIdentifier(), false), nil
return NewPlugin(nodeidentifier.NewDefaultNodeIdentifier()), nil
})
}
// NewPlugin creates a new NodeRestriction admission plugin.
// This plugin identifies requests from nodes
func NewPlugin(nodeIdentifier nodeidentifier.NodeIdentifier, strict bool) *nodePlugin {
func NewPlugin(nodeIdentifier nodeidentifier.NodeIdentifier) *nodePlugin {
return &nodePlugin{
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
nodeIdentifier: nodeIdentifier,
strict: strict,
}
}
// nodePlugin holds state for and implements the admission plugin.
type nodePlugin struct {
*admission.Handler
strict bool
nodeIdentifier nodeidentifier.NodeIdentifier
podsGetter coreinternalversion.PodsGetter
}
......@@ -92,12 +90,8 @@ func (c *nodePlugin) Admit(a admission.Attributes) error {
}
if len(nodeName) == 0 {
if c.strict {
// In strict mode, disallow requests from nodes we cannot match to a particular node
return admission.NewForbidden(a, fmt.Errorf("could not determine node identity from user"))
}
// Our job is just to restrict identifiable nodes
return nil
// disallow requests we cannot match to a particular node
return admission.NewForbidden(a, fmt.Errorf("could not determine node from user %s", a.GetUserInfo().GetName()))
}
switch a.GetResource().GroupResource() {
......
......@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package node
package noderestriction
import (
"strings"
......@@ -82,7 +82,6 @@ func Test_nodePlugin_Admit(t *testing.T) {
tests := []struct {
name string
strict bool
podsGetter coreinternalversion.PodsGetter
attributes admission.Attributes
err string
......@@ -473,7 +472,7 @@ func Test_nodePlugin_Admit(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := NewPlugin(nodeidentifier.NewDefaultNodeIdentifier(), tt.strict)
c := NewPlugin(nodeidentifier.NewDefaultNodeIdentifier())
c.podsGetter = tt.podsGetter
err := c.Admit(tt.attributes)
if (err == nil) != (len(tt.err) == 0) {
......
......@@ -24,6 +24,7 @@ filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//plugin/pkg/auth/authorizer/node:all-srcs",
"//plugin/pkg/auth/authorizer/rbac:all-srcs",
],
tags = ["automanaged"],
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["node_authorizer_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/auth/nodeidentifier:go_default_library",
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"graph.go",
"graph_populator.go",
"node_authorizer.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/persistentvolume:go_default_library",
"//pkg/api/pod:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/auth/nodeidentifier:go_default_library",
"//pkg/client/informers/informers_generated/internalversion/core/internalversion:go_default_library",
"//plugin/pkg/auth/authorizer/rbac:go_default_library",
"//third_party/forked/gonum/graph:go_default_library",
"//third_party/forked/gonum/graph/simple:go_default_library",
"//third_party/forked/gonum/graph/traverse:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
approvers:
- timstclair
- liggitt
- deads2k
reviewers:
- timstclair
- liggitt
- deads2k
- ericchiang
/*
Copyright 2017 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 node
import (
"sync"
"k8s.io/kubernetes/pkg/api"
pvutil "k8s.io/kubernetes/pkg/api/persistentvolume"
podutil "k8s.io/kubernetes/pkg/api/pod"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
"k8s.io/kubernetes/third_party/forked/gonum/graph/simple"
)
// namedVertex implements graph.Node and remembers the type, namespace, and name of its related API object
type namedVertex struct {
name string
namespace string
id int
vertexType vertexType
}
func newNamedVertex(vertexType vertexType, namespace, name string, id int) *namedVertex {
return &namedVertex{
vertexType: vertexType,
name: name,
namespace: namespace,
id: id,
}
}
func (n *namedVertex) ID() int {
return n.id
}
func (n *namedVertex) String() string {
if len(n.namespace) == 0 {
return vertexTypes[n.vertexType] + ":" + n.name
}
return vertexTypes[n.vertexType] + ":" + n.namespace + "/" + n.name
}
// destinationEdge is a graph edge that includes a denormalized reference to the final destination vertex.
// This should only be used when there is a single leaf vertex reachable from T.
type destinationEdge struct {
F graph.Node
T graph.Node
Destination graph.Node
}
func newDestinationEdge(from, to, destination graph.Node) graph.Edge {
return &destinationEdge{F: from, T: to, Destination: destination}
}
func (e *destinationEdge) From() graph.Node { return e.F }
func (e *destinationEdge) To() graph.Node { return e.T }
func (e *destinationEdge) Weight() float64 { return 0 }
func (e *destinationEdge) DestinationID() int { return e.Destination.ID() }
// Graph holds graph vertices and a way to look up a vertex for a particular API type/namespace/name.
// All edges point toward the vertices representing Kubernetes nodes:
//
// node <- pod
// pod <- secret,configmap,pvc
// pvc <- pv
// pv <- secret
type Graph struct {
lock sync.RWMutex
graph *simple.DirectedAcyclicGraph
// vertices is a map of type -> namespace -> name -> vertex
vertices map[vertexType]namespaceVertexMapping
}
// namespaceVertexMapping is a map of namespace -> name -> vertex
type namespaceVertexMapping map[string]nameVertexMapping
// nameVertexMapping is a map of name -> vertex
type nameVertexMapping map[string]*namedVertex
func NewGraph() *Graph {
return &Graph{
vertices: map[vertexType]namespaceVertexMapping{},
graph: simple.NewDirectedAcyclicGraph(0, 0),
}
}
// vertexType indicates the type of the API object the vertex represents.
// represented as a byte to minimize space used in the vertices.
type vertexType byte
const (
configMapVertexType vertexType = iota
nodeVertexType
podVertexType
pvcVertexType
pvVertexType
secretVertexType
)
var vertexTypes = map[vertexType]string{
configMapVertexType: "configmap",
nodeVertexType: "node",
podVertexType: "pod",
pvcVertexType: "pvc",
pvVertexType: "pv",
secretVertexType: "secret",
}
// must be called under a write lock
func (g *Graph) getOrCreateVertex_locked(vertexType vertexType, namespace, name string) *namedVertex {
if vertex, exists := g.getVertex_rlocked(vertexType, namespace, name); exists {
return vertex
}
return g.createVertex_locked(vertexType, namespace, name)
}
// must be called under a read lock
func (g *Graph) getVertex_rlocked(vertexType vertexType, namespace, name string) (*namedVertex, bool) {
vertex, exists := g.vertices[vertexType][namespace][name]
return vertex, exists
}
// must be called under a write lock
func (g *Graph) createVertex_locked(vertexType vertexType, namespace, name string) *namedVertex {
typedVertices, exists := g.vertices[vertexType]
if !exists {
typedVertices = namespaceVertexMapping{}
g.vertices[vertexType] = typedVertices
}
namespacedVertices, exists := typedVertices[namespace]
if !exists {
namespacedVertices = map[string]*namedVertex{}
typedVertices[namespace] = namespacedVertices
}
vertex := newNamedVertex(vertexType, namespace, name, g.graph.NewNodeID())
namespacedVertices[name] = vertex
g.graph.AddNode(vertex)
return vertex
}
// must be called under write lock
func (g *Graph) deleteVertex_locked(vertexType vertexType, namespace, name string) {
vertex, exists := g.getVertex_rlocked(vertexType, namespace, name)
if !exists {
return
}
// find existing neighbors with a single edge (meaning we are their only neighbor)
neighborsToRemove := []graph.Node{}
g.graph.VisitFrom(vertex, func(neighbor graph.Node) bool {
// this downstream neighbor has only one edge (which must be from us), so remove them as well
if g.graph.Degree(neighbor) == 1 {
neighborsToRemove = append(neighborsToRemove, neighbor)
}
return true
})
g.graph.VisitTo(vertex, func(neighbor graph.Node) bool {
// this upstream neighbor has only one edge (which must be to us), so remove them as well
if g.graph.Degree(neighbor) == 1 {
neighborsToRemove = append(neighborsToRemove, neighbor)
}
return true
})
// remove the vertex
g.graph.RemoveNode(vertex)
delete(g.vertices[vertexType][namespace], name)
if len(g.vertices[vertexType][namespace]) == 0 {
delete(g.vertices[vertexType], namespace)
}
// remove neighbors that are now edgeless
for _, neighbor := range neighborsToRemove {
g.graph.RemoveNode(neighbor)
n := neighbor.(*namedVertex)
delete(g.vertices[n.vertexType][n.namespace], n.name)
if len(g.vertices[n.vertexType][n.namespace]) == 0 {
delete(g.vertices[n.vertexType], n.namespace)
}
}
}
// AddPod should only be called once spec.NodeName is populated.
// It sets up edges for the following relationships (which are immutable for a pod once bound to a node):
//
// pod -> node
//
// secret -> pod
// configmap -> pod
// pvc -> pod
func (g *Graph) AddPod(pod *api.Pod) {
g.lock.Lock()
defer g.lock.Unlock()
g.deleteVertex_locked(podVertexType, pod.Namespace, pod.Name)
podVertex := g.getOrCreateVertex_locked(podVertexType, pod.Namespace, pod.Name)
nodeVertex := g.getOrCreateVertex_locked(nodeVertexType, "", pod.Spec.NodeName)
g.graph.SetEdge(newDestinationEdge(podVertex, nodeVertex, nodeVertex))
podutil.VisitPodSecretNames(pod, func(secret string) bool {
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(secretVertexType, pod.Namespace, secret), podVertex, nodeVertex))
return true
})
podutil.VisitPodConfigmapNames(pod, func(configmap string) bool {
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(configMapVertexType, pod.Namespace, configmap), podVertex, nodeVertex))
return true
})
for _, v := range pod.Spec.Volumes {
if v.PersistentVolumeClaim != nil {
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(pvcVertexType, pod.Namespace, v.PersistentVolumeClaim.ClaimName), podVertex, nodeVertex))
}
}
}
func (g *Graph) DeletePod(name, namespace string) {
g.lock.Lock()
defer g.lock.Unlock()
g.deleteVertex_locked(podVertexType, namespace, name)
}
// AddPV sets up edges for the following relationships:
//
// secret -> pv
//
// pv -> pvc
func (g *Graph) AddPV(pv *api.PersistentVolume) {
g.lock.Lock()
defer g.lock.Unlock()
// clear existing edges
g.deleteVertex_locked(pvVertexType, "", pv.Name)
// if we have a pvc, establish new edges
if pv.Spec.ClaimRef != nil {
pvVertex := g.getOrCreateVertex_locked(pvVertexType, "", pv.Name)
// since we don't know the other end of the pvc -> pod -> node chain (or it may not even exist yet), we can't decorate these edges with kubernetes node info
g.graph.SetEdge(simple.Edge{F: pvVertex, T: g.getOrCreateVertex_locked(pvcVertexType, pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name)})
pvutil.VisitPVSecretNames(pv, func(secret string) bool {
// This grants access to the named secret in the same namespace as the bound PVC
g.graph.SetEdge(simple.Edge{F: g.getOrCreateVertex_locked(secretVertexType, pv.Spec.ClaimRef.Namespace, secret), T: pvVertex})
return true
})
}
}
func (g *Graph) DeletePV(name string) {
g.lock.Lock()
defer g.lock.Unlock()
g.deleteVertex_locked(pvVertexType, "", name)
}
/*
Copyright 2017 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 node
import (
"github.com/golang/glog"
"k8s.io/client-go/tools/cache"
"k8s.io/kubernetes/pkg/api"
coreinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion/core/internalversion"
)
type graphPopulator struct {
graph *Graph
}
func AddGraphEventHandlers(graph *Graph, pods coreinformers.PodInformer, pvs coreinformers.PersistentVolumeInformer) {
g := &graphPopulator{
graph: graph,
}
pods.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: g.addPod,
UpdateFunc: g.updatePod,
DeleteFunc: g.deletePod,
})
pvs.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: g.addPV,
UpdateFunc: g.updatePV,
DeleteFunc: g.deletePV,
})
}
func (g *graphPopulator) addPod(obj interface{}) {
g.updatePod(nil, obj)
}
func (g *graphPopulator) updatePod(oldObj, obj interface{}) {
pod := obj.(*api.Pod)
if len(pod.Spec.NodeName) == 0 {
// No node assigned
glog.V(5).Infof("updatePod %s/%s, no node", pod.Namespace, pod.Name)
return
}
if oldPod, ok := oldObj.(*api.Pod); ok && oldPod != nil {
if (pod.Spec.NodeName == oldPod.Spec.NodeName) && (pod.UID == oldPod.UID) {
// Node and uid are unchanged, all object references in the pod spec are immutable
glog.V(5).Infof("updatePod %s/%s, node unchanged", pod.Namespace, pod.Name)
return
}
}
glog.V(4).Infof("updatePod %s/%s for node %s", pod.Namespace, pod.Name, pod.Spec.NodeName)
g.graph.AddPod(pod)
}
func (g *graphPopulator) deletePod(obj interface{}) {
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
obj = tombstone.Obj
}
pod, ok := obj.(*api.Pod)
if !ok {
glog.Infof("unexpected type %T", obj)
return
}
if len(pod.Spec.NodeName) == 0 {
glog.V(5).Infof("deletePod %s/%s, no node", pod.Namespace, pod.Name)
return
}
glog.V(4).Infof("deletePod %s/%s for node %s", pod.Namespace, pod.Name, pod.Spec.NodeName)
g.graph.DeletePod(pod.Name, pod.Namespace)
}
func (g *graphPopulator) addPV(obj interface{}) {
g.updatePV(nil, obj)
}
func (g *graphPopulator) updatePV(oldObj, obj interface{}) {
pv := obj.(*api.PersistentVolume)
// TODO: skip add if uid, pvc, and secrets are all identical between old and new
g.graph.AddPV(pv)
}
func (g *graphPopulator) deletePV(obj interface{}) {
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
obj = tombstone.Obj
}
pv, ok := obj.(*api.PersistentVolume)
if !ok {
glog.Infof("unexpected type %T", obj)
return
}
g.graph.DeletePV(pv.Name)
}
/*
Copyright 2017 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 node
import (
"fmt"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/api"
rbacapi "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
"k8s.io/kubernetes/third_party/forked/gonum/graph/traverse"
)
// NodeAuthorizer authorizes requests from kubelets, with the following logic:
// 1. If a request is not from a node (IdentifyNode() returns isNode=false), reject
// 2. If a specific node cannot be identified (IdentifyNode() returns nodeName=""), reject
// 3. If a request is for a secret, configmap, persistent volume or persistent volume claim, reject unless the verb is get, and the requested object is related to the requesting node:
// node <- pod
// node <- pod <- secret
// node <- pod <- configmap
// node <- pod <- pvc
// node <- pod <- pvc <- pv
// node <- pod <- pvc <- pv <- secret
// 4. For other resources, authorize all nodes uniformly using statically defined rules
type NodeAuthorizer struct {
graph *Graph
identifier nodeidentifier.NodeIdentifier
nodeRules []rbacapi.PolicyRule
}
// New returns a new node authorizer
func NewAuthorizer(graph *Graph, identifier nodeidentifier.NodeIdentifier, rules []rbacapi.PolicyRule) authorizer.Authorizer {
return &NodeAuthorizer{
graph: graph,
identifier: identifier,
nodeRules: rules,
}
}
var (
configMapResource = api.Resource("configmaps")
secretResource = api.Resource("secrets")
pvcResource = api.Resource("persistentvolumeclaims")
pvResource = api.Resource("persistentvolumes")
)
func (r *NodeAuthorizer) Authorize(attrs authorizer.Attributes) (bool, string, error) {
nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser())
if !isNode {
// reject requests from non-nodes
return false, "", nil
}
if len(nodeName) == 0 {
// reject requests from unidentifiable nodes
glog.V(2).Infof("NODE DENY: unknown node for user %q", attrs.GetUser().GetName())
return false, fmt.Sprintf("unknown node for user %q", attrs.GetUser().GetName()), nil
}
// subdivide access to specific resources
if attrs.IsResourceRequest() {
requestResource := schema.GroupResource{Group: attrs.GetAPIGroup(), Resource: attrs.GetResource()}
switch requestResource {
case secretResource:
return r.authorizeGet(nodeName, secretVertexType, attrs)
case configMapResource:
return r.authorizeGet(nodeName, configMapVertexType, attrs)
case pvcResource:
return r.authorizeGet(nodeName, pvcVertexType, attrs)
case pvResource:
return r.authorizeGet(nodeName, pvVertexType, attrs)
}
}
// Access to other resources is not subdivided, so just evaluate against the statically defined node rules
return rbac.RulesAllow(attrs, r.nodeRules...), "", nil
}
// authorizeGet authorizes "get" requests to objects of the specified type if they are related to the specified node
func (r *NodeAuthorizer) authorizeGet(nodeName string, startingType vertexType, attrs authorizer.Attributes) (bool, string, error) {
if attrs.GetVerb() != "get" || len(attrs.GetName()) == 0 {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return false, "can only get individual resources of this type", nil
}
if len(attrs.GetSubresource()) > 0 {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return false, "cannot get subresource", nil
}
ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName())
if err != nil {
glog.V(2).Infof("NODE DENY: %v", err)
return false, "no path found to object", nil
}
if !ok {
glog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs)
return false, "no path found to object", nil
}
return ok, "", nil
}
// hasPathFrom returns true if there is a directed path from the specified type/namespace/name to the specified Node
func (r *NodeAuthorizer) hasPathFrom(nodeName string, startingType vertexType, startingNamespace, startingName string) (bool, error) {
r.graph.lock.RLock()
defer r.graph.lock.RUnlock()
nodeVertex, exists := r.graph.getVertex_rlocked(nodeVertexType, "", nodeName)
if !exists {
return false, fmt.Errorf("unknown node %s cannot get %s %s/%s", nodeName, vertexTypes[startingType], startingNamespace, startingName)
}
startingVertex, exists := r.graph.getVertex_rlocked(startingType, startingNamespace, startingName)
if !exists {
return false, fmt.Errorf("node %s cannot get unknown %s %s/%s", nodeName, vertexTypes[startingType], startingNamespace, startingName)
}
found := false
traversal := &traverse.VisitingDepthFirst{
EdgeFilter: func(edge graph.Edge) bool {
if destinationEdge, ok := edge.(*destinationEdge); ok {
if destinationEdge.DestinationID() != nodeVertex.ID() {
// Don't follow edges leading to other nodes
return false
}
// We found an edge leading to the node we want
found = true
}
// Visit this edge
return true
},
}
traversal.Walk(r.graph.graph, startingVertex, func(n graph.Node) bool {
if n.ID() == nodeVertex.ID() {
// We found the node we want
found = true
}
// Stop visiting if we've found the node we want
return found
})
if !found {
return false, fmt.Errorf("node %s cannot get %s %s/%s, no path was found", nodeName, vertexTypes[startingType], startingNamespace, startingName)
}
return true, nil
}
......@@ -86,6 +86,50 @@ func addClusterRoleBindingLabel(rolebindings []rbac.ClusterRoleBinding) {
return
}
func NodeRules() []rbac.PolicyRule {
return []rbac.PolicyRule{
// Needed to check API access. These creates are non-mutating
rbac.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(),
rbac.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews", "localsubjectaccessreviews").RuleOrDie(),
// Needed to build serviceLister, to populate env vars for services
rbac.NewRule(Read...).Groups(legacyGroup).Resources("services").RuleOrDie(),
// Nodes can register Node API objects and report status.
// Use the NodeRestriction admission plugin to limit a node to creating/updating its own API object.
rbac.NewRule("create", "get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("update", "patch").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
rbac.NewRule("update", "patch", "delete").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
// TODO: restrict to the bound node as creator in the NodeRestrictions admission plugin
rbac.NewRule("create", "update", "patch").Groups(legacyGroup).Resources("events").RuleOrDie(),
// TODO: restrict to pods scheduled on the bound node once field selectors are supported by list/watch authorization
rbac.NewRule(Read...).Groups(legacyGroup).Resources("pods").RuleOrDie(),
// Needed for the node to create/delete mirror pods.
// Use the NodeRestriction admission plugin to limit a node to creating/deleting mirror pods bound to itself.
rbac.NewRule("create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
// Needed for the node to report status of pods it is running.
// Use the NodeRestriction admission plugin to limit a node to updating status of pods bound to itself.
rbac.NewRule("update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
// Needed for imagepullsecrets, rbd/ceph and secret volumes, and secrets in envs
// Needed for configmap volume and envs
// Use the NodeRestriction admission plugin to limit a node to get secrets/configmaps referenced by pods bound to itself.
rbac.NewRule("get").Groups(legacyGroup).Resources("secrets", "configmaps").RuleOrDie(),
// Needed for persistent volumes
// Use the NodeRestriction admission plugin to limit a node to get pv/pvc objects referenced by pods bound to itself.
rbac.NewRule("get").Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(),
// TODO: add to the Node authorizer and restrict to endpoints referenced by pods or PVs bound to the node
// Needed for glusterfs volumes
rbac.NewRule("get").Groups(legacyGroup).Resources("endpoints").RuleOrDie(),
// Used to create a certificatesigningrequest for a node-specific client certificate, and watch
// for it to be signed. This allows the kubelet to rotate it's own certificate.
rbac.NewRule("create", "get", "list", "watch").Groups(certificatesGroup).Resources("certificatesigningrequests").RuleOrDie(),
}
}
// ClusterRoles returns the cluster roles to bootstrap an API server with
func ClusterRoles() []rbac.ClusterRole {
roles := []rbac.ClusterRole{
......@@ -204,47 +248,7 @@ func ClusterRoles() []rbac.ClusterRole {
{
// a role for nodes to use to have the access they need for running pods
ObjectMeta: metav1.ObjectMeta{Name: "system:node"},
Rules: []rbac.PolicyRule{
// Needed to check API access. These creates are non-mutating
rbac.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(),
rbac.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews", "localsubjectaccessreviews").RuleOrDie(),
// Needed to build serviceLister, to populate env vars for services
rbac.NewRule(Read...).Groups(legacyGroup).Resources("services").RuleOrDie(),
// Nodes can register themselves
// TODO: restrict to creating a node with the same name they announce
rbac.NewRule("create", "get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
// TODO: restrict to the bound node once supported
rbac.NewRule("update", "patch").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
rbac.NewRule("update", "patch", "delete").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
// TODO: restrict to the bound node as creator once supported
rbac.NewRule("create", "update", "patch").Groups(legacyGroup).Resources("events").RuleOrDie(),
// TODO: restrict to pods scheduled on the bound node once supported
rbac.NewRule(Read...).Groups(legacyGroup).Resources("pods").RuleOrDie(),
// TODO: remove once mirror pods are removed
// TODO: restrict deletion to mirror pods created by the bound node once supported
// Needed for the node to create/delete mirror pods
rbac.NewRule("create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
// TODO: restrict to pods scheduled on the bound node once supported
rbac.NewRule("update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
// TODO: restrict to secrets and configmaps used by pods scheduled on bound node once supported
// Needed for imagepullsecrets, rbd/ceph and secret volumes, and secrets in envs
// Needed for configmap volume and envs
rbac.NewRule("get").Groups(legacyGroup).Resources("secrets", "configmaps").RuleOrDie(),
// TODO: restrict to claims/volumes used by pods scheduled on bound node once supported
// Needed for persistent volumes
rbac.NewRule("get").Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(),
// TODO: restrict to namespaces of pods scheduled on bound node once supported
// TODO: change glusterfs to use DNS lookup so this isn't needed?
// Needed for glusterfs volumes
rbac.NewRule("get").Groups(legacyGroup).Resources("endpoints").RuleOrDie(),
// Used to create a certificatesigningrequest for a node-specific client certificate, and watch
// for it to be signed. This allows the kubelet to rotate it's own certificate.
rbac.NewRule("create", "get", "list", "watch").Groups(certificatesGroup).Resources("certificatesigningrequests").RuleOrDie(),
},
Rules: NodeRules(),
},
{
// a role to use for node-problem-detector access. It does not get bound to default location since
......@@ -358,18 +362,70 @@ func ClusterRoles() []rbac.ClusterRole {
return roles
}
// ClusterRoleBindingFilter can modify and return or omit (by returning nil) a role binding
type ClusterRoleBindingFilter func(*rbac.ClusterRoleBinding) *rbac.ClusterRoleBinding
// AddClusterRoleBindingFilter adds the given filter to the list that is invoked when determing bootstrap roles to reconcile.
func AddClusterRoleBindingFilter(filter ClusterRoleBindingFilter) {
clusterRoleBindingFilters = append(clusterRoleBindingFilters, filter)
}
// ClearClusterRoleBindingFilters removes any filters added using AddClusterRoleBindingFilter
func ClearClusterRoleBindingFilters() {
clusterRoleBindingFilters = nil
}
const systemNodeRoleName = "system:node"
var clusterRoleBindingFilters []ClusterRoleBindingFilter
// OmitNodesGroupBinding is a filter that omits the deprecated binding for the system:nodes group to the system:node role.
var OmitNodesGroupBinding = ClusterRoleBindingFilter(func(binding *rbac.ClusterRoleBinding) *rbac.ClusterRoleBinding {
if binding.RoleRef.Name == systemNodeRoleName {
subjects := []rbac.Subject{}
for _, subject := range binding.Subjects {
if subject.Kind == rbac.GroupKind && subject.Name == user.NodesGroup {
continue
}
subjects = append(subjects, subject)
}
binding.Subjects = subjects
}
return binding
})
// ClusterRoleBindings return default rolebindings to the default roles
func ClusterRoleBindings() []rbac.ClusterRoleBinding {
rolebindings := []rbac.ClusterRoleBinding{
rbac.NewClusterBinding("cluster-admin").Groups(user.SystemPrivilegedGroup).BindingOrDie(),
rbac.NewClusterBinding("system:discovery").Groups(user.AllAuthenticated, user.AllUnauthenticated).BindingOrDie(),
rbac.NewClusterBinding("system:basic-user").Groups(user.AllAuthenticated, user.AllUnauthenticated).BindingOrDie(),
rbac.NewClusterBinding("system:node").Groups(user.NodesGroup).BindingOrDie(),
rbac.NewClusterBinding("system:node-proxier").Users(user.KubeProxy).BindingOrDie(),
rbac.NewClusterBinding("system:kube-controller-manager").Users(user.KubeControllerManager).BindingOrDie(),
rbac.NewClusterBinding("system:kube-dns").SAs("kube-system", "kube-dns").BindingOrDie(),
rbac.NewClusterBinding("system:kube-scheduler").Users(user.KubeScheduler).BindingOrDie(),
// This default system:nodes binding is deprecated in 1.7 with the availability of the Node authorizer.
// If an admin wants to grant the system:node role (which cannot partition Node API access), they will need to create their own clusterrolebinding.
// TODO: Remove the subjects from this binding in 1.8 (leave the empty binding for tightening reconciliation), and remove AddClusterRoleBindingFilter()
rbac.NewClusterBinding(systemNodeRoleName).Groups(user.NodesGroup).BindingOrDie(),
}
addClusterRoleBindingLabel(rolebindings)
return rolebindings
retval := []rbac.ClusterRoleBinding{}
for i := range rolebindings {
binding := &rolebindings[i]
for _, filter := range clusterRoleBindingFilters {
binding = filter(binding)
if binding == nil {
break
}
}
if binding != nil {
retval = append(retval, *binding)
}
}
return retval
}
......@@ -12,6 +12,7 @@ go_test(
srcs = [
"accessreview_test.go",
"auth_test.go",
"node_test.go",
"rbac_test.go",
],
tags = [
......@@ -27,7 +28,10 @@ go_test(
"//pkg/apis/extensions:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/auth/authorizer/abac:go_default_library",
"//pkg/auth/nodeidentifier:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/kubeapiserver/authorizer:go_default_library",
"//pkg/master:go_default_library",
"//pkg/registry/rbac/clusterrole:go_default_library",
"//pkg/registry/rbac/clusterrole/storage:go_default_library",
......@@ -38,10 +42,14 @@ go_test(
"//pkg/registry/rbac/rolebinding:go_default_library",
"//pkg/registry/rbac/rolebinding/storage:go_default_library",
"//plugin/pkg/admission/admit:go_default_library",
"//plugin/pkg/admission/noderestriction:go_default_library",
"//plugin/pkg/auth/authorizer/rbac:go_default_library",
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
"//test/integration:go_default_library",
"//test/integration/framework:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
......
......@@ -20,6 +20,7 @@ filegroup(
"//third_party/forked/golang/expansion:all-srcs",
"//third_party/forked/golang/reflect:all-srcs",
"//third_party/forked/golang/template:all-srcs",
"//third_party/forked/gonum/graph:all-srcs",
"//third_party/htpasswd:all-srcs",
],
tags = ["automanaged"],
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["graph.go"],
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//third_party/forked/gonum/graph/internal/linear:all-srcs",
"//third_party/forked/gonum/graph/simple:all-srcs",
"//third_party/forked/gonum/graph/traverse:all-srcs",
],
tags = ["automanaged"],
)
Copyright ©2013 The gonum Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the gonum project nor the names of its authors and
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
Forked from gonum/graph@50b27dea7ebbfb052dfaf91681afc6fde28d8796 to support memory-use improvements to the simple graph
// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package graph
// Node is a graph node. It returns a graph-unique integer ID.
type Node interface {
ID() int
}
// Edge is a graph edge. In directed graphs, the direction of the
// edge is given from -> to, otherwise the edge is semantically
// unordered.
type Edge interface {
From() Node
To() Node
Weight() float64
}
// Graph is a generalized graph.
type Graph interface {
// Has returns whether the node exists within the graph.
Has(Node) bool
// Nodes returns all the nodes in the graph.
Nodes() []Node
// From returns all nodes that can be reached directly
// from the given node.
From(Node) []Node
// HasEdgeBeteen returns whether an edge exists between
// nodes x and y without considering direction.
HasEdgeBetween(x, y Node) bool
// Edge returns the edge from u to v if such an edge
// exists and nil otherwise. The node v must be directly
// reachable from u as defined by the From method.
Edge(u, v Node) Edge
}
// Undirected is an undirected graph.
type Undirected interface {
Graph
// EdgeBetween returns the edge between nodes x and y.
EdgeBetween(x, y Node) Edge
}
// Directed is a directed graph.
type Directed interface {
Graph
// HasEdgeFromTo returns whether an edge exists
// in the graph from u to v.
HasEdgeFromTo(u, v Node) bool
// To returns all nodes that can reach directly
// to the given node.
To(Node) []Node
}
// Weighter defines graphs that can report edge weights.
type Weighter interface {
// Weight returns the weight for the edge between
// x and y if Edge(x, y) returns a non-nil Edge.
// If x and y are the same node or there is no
// joining edge between the two nodes the weight
// value returned is implementation dependent.
// Weight returns true if an edge exists between
// x and y or if x and y have the same ID, false
// otherwise.
Weight(x, y Node) (w float64, ok bool)
}
// NodeAdder is an interface for adding arbitrary nodes to a graph.
type NodeAdder interface {
// NewNodeID returns a new unique arbitrary ID.
NewNodeID() int
// Adds a node to the graph. AddNode panics if
// the added node ID matches an existing node ID.
AddNode(Node)
}
// NodeRemover is an interface for removing nodes from a graph.
type NodeRemover interface {
// RemoveNode removes a node from the graph, as
// well as any edges attached to it. If the node
// is not in the graph it is a no-op.
RemoveNode(Node)
}
// EdgeSetter is an interface for adding edges to a graph.
type EdgeSetter interface {
// SetEdge adds an edge from one node to another.
// If the graph supports node addition the nodes
// will be added if they do not exist, otherwise
// SetEdge will panic.
// If the IDs returned by e.From and e.To are
// equal, SetEdge will panic.
SetEdge(e Edge)
}
// EdgeRemover is an interface for removing nodes from a graph.
type EdgeRemover interface {
// RemoveEdge removes the given edge, leaving the
// terminal nodes. If the edge does not exist it
// is a no-op.
RemoveEdge(Edge)
}
// Builder is a graph that can have nodes and edges added.
type Builder interface {
NodeAdder
EdgeSetter
}
// UndirectedBuilder is an undirected graph builder.
type UndirectedBuilder interface {
Undirected
Builder
}
// DirectedBuilder is a directed graph builder.
type DirectedBuilder interface {
Directed
Builder
}
// Copy copies nodes and edges as undirected edges from the source to the destination
// without first clearing the destination. Copy will panic if a node ID in the source
// graph matches a node ID in the destination.
//
// If the source is undirected and the destination is directed both directions will
// be present in the destination after the copy is complete.
//
// If the source is a directed graph, the destination is undirected, and a fundamental
// cycle exists with two nodes where the edge weights differ, the resulting destination
// graph's edge weight between those nodes is undefined. If there is a defined function
// to resolve such conflicts, an Undirect may be used to do this.
func Copy(dst Builder, src Graph) {
nodes := src.Nodes()
for _, n := range nodes {
dst.AddNode(n)
}
for _, u := range nodes {
for _, v := range src.From(u) {
dst.SetEdge(src.Edge(u, v))
}
}
}
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["linear.go"],
tags = ["automanaged"],
deps = ["//third_party/forked/gonum/graph:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
// Copyright ©2015 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package linear provides common linear data structures.
package linear
import (
"k8s.io/kubernetes/third_party/forked/gonum/graph"
)
// NodeStack implements a LIFO stack of graph.Node.
type NodeStack []graph.Node
// Len returns the number of graph.Nodes on the stack.
func (s *NodeStack) Len() int { return len(*s) }
// Pop returns the last graph.Node on the stack and removes it
// from the stack.
func (s *NodeStack) Pop() graph.Node {
v := *s
v, n := v[:len(v)-1], v[len(v)-1]
*s = v
return n
}
// Push adds the node n to the stack at the last position.
func (s *NodeStack) Push(n graph.Node) { *s = append(*s, n) }
// NodeQueue implements a FIFO queue.
type NodeQueue struct {
head int
data []graph.Node
}
// Len returns the number of graph.Nodes in the queue.
func (q *NodeQueue) Len() int { return len(q.data) - q.head }
// Enqueue adds the node n to the back of the queue.
func (q *NodeQueue) Enqueue(n graph.Node) {
if len(q.data) == cap(q.data) && q.head > 0 {
l := q.Len()
copy(q.data, q.data[q.head:])
q.head = 0
q.data = append(q.data[:l], n)
} else {
q.data = append(q.data, n)
}
}
// Dequeue returns the graph.Node at the front of the queue and
// removes it from the queue.
func (q *NodeQueue) Dequeue() graph.Node {
if q.Len() == 0 {
panic("queue: empty queue")
}
var n graph.Node
n, q.data[q.head] = q.data[q.head], nil
q.head++
if q.Len() == 0 {
q.head = 0
q.data = q.data[:0]
}
return n
}
// Reset clears the queue for reuse.
func (q *NodeQueue) Reset() {
q.head = 0
q.data = q.data[:0]
}
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"directed_acyclic_test.go",
"edgeholder_test.go",
"undirected_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = ["//third_party/forked/gonum/graph:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"directed_acyclic.go",
"edgeholder.go",
"simple.go",
"undirected.go",
],
tags = ["automanaged"],
deps = [
"//third_party/forked/gonum/graph:go_default_library",
"//vendor/golang.org/x/tools/container/intsets:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
package simple
import (
"k8s.io/kubernetes/third_party/forked/gonum/graph"
)
// DirectedAcyclicGraph implements graph.Directed using UndirectedGraph,
// which only stores one edge for any node pair.
type DirectedAcyclicGraph struct {
*UndirectedGraph
}
func NewDirectedAcyclicGraph(self, absent float64) *DirectedAcyclicGraph {
return &DirectedAcyclicGraph{
UndirectedGraph: NewUndirectedGraph(self, absent),
}
}
func (g *DirectedAcyclicGraph) HasEdgeFromTo(u, v graph.Node) bool {
edge := g.UndirectedGraph.EdgeBetween(u, v)
if edge == nil {
return false
}
return (edge.From().ID() == u.ID())
}
func (g *DirectedAcyclicGraph) From(n graph.Node) []graph.Node {
if !g.Has(n) {
return nil
}
fid := n.ID()
nodes := make([]graph.Node, 0, g.UndirectedGraph.edges[n.ID()].Len())
g.UndirectedGraph.edges[n.ID()].Visit(func(neighbor int, edge graph.Edge) {
if edge.From().ID() == fid {
nodes = append(nodes, g.UndirectedGraph.nodes[edge.To().ID()])
}
})
return nodes
}
func (g *DirectedAcyclicGraph) VisitFrom(n graph.Node, visitor func(neighbor graph.Node) (shouldContinue bool)) {
if !g.Has(n) {
return
}
fid := n.ID()
g.UndirectedGraph.edges[n.ID()].Visit(func(neighbor int, edge graph.Edge) {
if edge.From().ID() == fid {
if !visitor(g.UndirectedGraph.nodes[edge.To().ID()]) {
return
}
}
})
}
func (g *DirectedAcyclicGraph) To(n graph.Node) []graph.Node {
if !g.Has(n) {
return nil
}
tid := n.ID()
nodes := make([]graph.Node, 0, g.UndirectedGraph.edges[n.ID()].Len())
g.UndirectedGraph.edges[n.ID()].Visit(func(neighbor int, edge graph.Edge) {
if edge.To().ID() == tid {
nodes = append(nodes, g.UndirectedGraph.nodes[edge.From().ID()])
}
})
return nodes
}
func (g *DirectedAcyclicGraph) VisitTo(n graph.Node, visitor func(neighbor graph.Node) (shouldContinue bool)) {
if !g.Has(n) {
return
}
tid := n.ID()
g.UndirectedGraph.edges[n.ID()].Visit(func(neighbor int, edge graph.Edge) {
if edge.To().ID() == tid {
if !visitor(g.UndirectedGraph.nodes[edge.From().ID()]) {
return
}
}
})
}
// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package simple
import (
"math"
"testing"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
)
var _ graph.Graph = &DirectedAcyclicGraph{}
var _ graph.Directed = &DirectedAcyclicGraph{}
// Tests Issue #27
func TestAcyclicEdgeOvercounting(t *testing.T) {
g := generateDummyAcyclicGraph()
if neigh := g.From(Node(Node(2))); len(neigh) != 2 {
t.Errorf("Node 2 has incorrect number of neighbors got neighbors %v (count %d), expected 2 neighbors {0,1}", neigh, len(neigh))
}
}
func generateDummyAcyclicGraph() *DirectedAcyclicGraph {
nodes := [4]struct{ srcID, targetID int }{
{2, 1},
{1, 0},
{0, 2},
{2, 0},
}
g := NewDirectedAcyclicGraph(0, math.Inf(1))
for _, n := range nodes {
g.SetEdge(Edge{F: Node(n.srcID), T: Node(n.targetID), W: 1})
}
return g
}
// Test for issue #123 https://github.com/gonum/graph/issues/123
func TestAcyclicIssue123DirectedGraph(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("unexpected panic: %v", r)
}
}()
g := NewDirectedAcyclicGraph(0, math.Inf(1))
n0 := Node(g.NewNodeID())
g.AddNode(n0)
n1 := Node(g.NewNodeID())
g.AddNode(n1)
g.RemoveNode(n0)
n2 := Node(g.NewNodeID())
g.AddNode(n2)
}
package simple
import "k8s.io/kubernetes/third_party/forked/gonum/graph"
// edgeHolder represents a set of edges, with no more than one edge to or from a particular neighbor node
type edgeHolder interface {
// Visit invokes visitor with each edge and the id of the neighbor node in the edge
Visit(visitor func(neighbor int, edge graph.Edge))
// Delete removes edges to or from the specified neighbor
Delete(neighbor int) edgeHolder
// Set stores the edge to or from the specified neighbor
Set(neighbor int, edge graph.Edge) edgeHolder
// Get returns the edge to or from the specified neighbor
Get(neighbor int) (graph.Edge, bool)
// Len returns the number of edges
Len() int
}
// sliceEdgeHolder holds a list of edges to or from self
type sliceEdgeHolder struct {
self int
edges []graph.Edge
}
func (e *sliceEdgeHolder) Visit(visitor func(neighbor int, edge graph.Edge)) {
for _, edge := range e.edges {
if edge.From().ID() == e.self {
visitor(edge.To().ID(), edge)
} else {
visitor(edge.From().ID(), edge)
}
}
}
func (e *sliceEdgeHolder) Delete(neighbor int) edgeHolder {
edges := e.edges[:0]
for i, edge := range e.edges {
if edge.From().ID() == e.self {
if edge.To().ID() == neighbor {
continue
}
} else {
if edge.From().ID() == neighbor {
continue
}
}
edges = append(edges, e.edges[i])
}
e.edges = edges
return e
}
func (e *sliceEdgeHolder) Set(neighbor int, newEdge graph.Edge) edgeHolder {
for i, edge := range e.edges {
if edge.From().ID() == e.self {
if edge.To().ID() == neighbor {
e.edges[i] = newEdge
return e
}
} else {
if edge.From().ID() == neighbor {
e.edges[i] = newEdge
return e
}
}
}
if len(e.edges) < 4 {
e.edges = append(e.edges, newEdge)
return e
}
h := mapEdgeHolder(make(map[int]graph.Edge, len(e.edges)+1))
for i, edge := range e.edges {
if edge.From().ID() == e.self {
h[edge.To().ID()] = e.edges[i]
} else {
h[edge.From().ID()] = e.edges[i]
}
}
h[neighbor] = newEdge
return h
}
func (e *sliceEdgeHolder) Get(neighbor int) (graph.Edge, bool) {
for _, edge := range e.edges {
if edge.From().ID() == e.self {
if edge.To().ID() == neighbor {
return edge, true
}
} else {
if edge.From().ID() == neighbor {
return edge, true
}
}
}
return nil, false
}
func (e *sliceEdgeHolder) Len() int {
return len(e.edges)
}
// mapEdgeHolder holds a map of neighbors to edges
type mapEdgeHolder map[int]graph.Edge
func (e mapEdgeHolder) Visit(visitor func(neighbor int, edge graph.Edge)) {
for neighbor, edge := range e {
visitor(neighbor, edge)
}
}
func (e mapEdgeHolder) Delete(neighbor int) edgeHolder {
delete(e, neighbor)
return e
}
func (e mapEdgeHolder) Set(neighbor int, edge graph.Edge) edgeHolder {
e[neighbor] = edge
return e
}
func (e mapEdgeHolder) Get(neighbor int) (graph.Edge, bool) {
edge, ok := e[neighbor]
return edge, ok
}
func (e mapEdgeHolder) Len() int {
return len(e)
}
package simple
import (
"reflect"
"sort"
"testing"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
)
func TestEdgeHolder(t *testing.T) {
holder := edgeHolder(&sliceEdgeHolder{self: 1})
// Empty tests
if len := holder.Len(); len != 0 {
t.Errorf("expected 0")
}
if n, ok := holder.Get(2); ok || n != nil {
t.Errorf("expected nil,false")
}
holder.Visit(func(_ int, _ graph.Edge) { t.Errorf("unexpected call to visitor") })
holder = holder.Delete(2)
// Insert an edge to ourselves
holder = holder.Set(1, Edge{F: Node(1), T: Node(1)})
if len := holder.Len(); len != 1 {
t.Errorf("expected 1")
}
if n, ok := holder.Get(1); !ok || n == nil || n.From().ID() != 1 || n.To().ID() != 1 {
t.Errorf("expected edge to ourselves, got %#v", n)
}
neighbors := []int{}
holder.Visit(func(neighbor int, _ graph.Edge) { neighbors = append(neighbors, neighbor) })
if !reflect.DeepEqual(neighbors, []int{1}) {
t.Errorf("expected a single visit to ourselves, got %v", neighbors)
}
// Insert edges from us to other nodes
holder = holder.Set(2, Edge{F: Node(1), T: Node(2)})
holder = holder.Set(3, Edge{F: Node(1), T: Node(3)})
holder = holder.Set(4, Edge{F: Node(1), T: Node(4)})
if len := holder.Len(); len != 4 {
t.Errorf("expected 4")
}
if n, ok := holder.Get(2); !ok || n == nil || n.From().ID() != 1 || n.To().ID() != 2 {
t.Errorf("expected edge from us to another node, got %#v", n)
}
neighbors = []int{}
holder.Visit(func(neighbor int, _ graph.Edge) { neighbors = append(neighbors, neighbor) })
if !reflect.DeepEqual(neighbors, []int{1, 2, 3, 4}) {
t.Errorf("expected a single visit to ourselves, got %v", neighbors)
}
// Insert edges to us to other nodes
holder = holder.Set(2, Edge{F: Node(2), T: Node(1)})
holder = holder.Set(3, Edge{F: Node(3), T: Node(1)})
holder = holder.Set(4, Edge{F: Node(4), T: Node(1)})
if len := holder.Len(); len != 4 {
t.Errorf("expected 4")
}
if n, ok := holder.Get(2); !ok || n == nil || n.From().ID() != 2 || n.To().ID() != 1 {
t.Errorf("expected reversed edge, got %#v", n)
}
neighbors = []int{}
holder.Visit(func(neighbor int, _ graph.Edge) { neighbors = append(neighbors, neighbor) })
if !reflect.DeepEqual(neighbors, []int{1, 2, 3, 4}) {
t.Errorf("expected a single visit to ourselves, got %v", neighbors)
}
if _, ok := holder.(*sliceEdgeHolder); !ok {
t.Errorf("expected slice edge holder")
}
// Make the transition to a map
holder = holder.Set(5, Edge{F: Node(5), T: Node(1)})
if _, ok := holder.(mapEdgeHolder); !ok {
t.Errorf("expected map edge holder")
}
if len := holder.Len(); len != 5 {
t.Errorf("expected 5")
}
if n, ok := holder.Get(2); !ok || n == nil || n.From().ID() != 2 || n.To().ID() != 1 {
t.Errorf("expected old edges, got %#v", n)
}
if n, ok := holder.Get(5); !ok || n == nil || n.From().ID() != 5 || n.To().ID() != 1 {
t.Errorf("expected new edge, got %#v", n)
}
neighbors = []int{}
holder.Visit(func(neighbor int, _ graph.Edge) { neighbors = append(neighbors, neighbor) })
sort.Ints(neighbors) // sort, map order is random
if !reflect.DeepEqual(neighbors, []int{1, 2, 3, 4, 5}) {
t.Errorf("expected 1,2,3,4,5, got %v", neighbors)
}
holder = holder.Delete(1)
holder = holder.Delete(2)
holder = holder.Delete(3)
holder = holder.Delete(4)
holder = holder.Delete(5)
holder = holder.Delete(6)
if len := holder.Len(); len != 0 {
t.Errorf("expected 0")
}
}
// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package simple provides a suite of simple graph implementations satisfying
// the gonum/graph interfaces.
package simple
import (
"math"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
)
// Node is a simple graph node.
type Node int
// ID returns the ID number of the node.
func (n Node) ID() int {
return int(n)
}
// Edge is a simple graph edge.
type Edge struct {
F, T graph.Node
W float64
}
// From returns the from-node of the edge.
func (e Edge) From() graph.Node { return e.F }
// To returns the to-node of the edge.
func (e Edge) To() graph.Node { return e.T }
// Weight returns the weight of the edge.
func (e Edge) Weight() float64 { return e.W }
// maxInt is the maximum value of the machine-dependent int type.
const maxInt int = int(^uint(0) >> 1)
// isSame returns whether two float64 values are the same where NaN values
// are equalable.
func isSame(a, b float64) bool {
return a == b || (math.IsNaN(a) && math.IsNaN(b))
}
// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package simple
import (
"fmt"
"golang.org/x/tools/container/intsets"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
)
// UndirectedGraph implements a generalized undirected graph.
type UndirectedGraph struct {
nodes map[int]graph.Node
edges map[int]edgeHolder
self, absent float64
freeIDs intsets.Sparse
usedIDs intsets.Sparse
}
// NewUndirectedGraph returns an UndirectedGraph with the specified self and absent
// edge weight values.
func NewUndirectedGraph(self, absent float64) *UndirectedGraph {
return &UndirectedGraph{
nodes: make(map[int]graph.Node),
edges: make(map[int]edgeHolder),
self: self,
absent: absent,
}
}
// NewNodeID returns a new unique ID for a node to be added to g. The returned ID does
// not become a valid ID in g until it is added to g.
func (g *UndirectedGraph) NewNodeID() int {
if len(g.nodes) == 0 {
return 0
}
if len(g.nodes) == maxInt {
panic(fmt.Sprintf("simple: cannot allocate node: no slot"))
}
var id int
if g.freeIDs.Len() != 0 && g.freeIDs.TakeMin(&id) {
return id
}
if id = g.usedIDs.Max(); id < maxInt {
return id + 1
}
for id = 0; id < maxInt; id++ {
if !g.usedIDs.Has(id) {
return id
}
}
panic("unreachable")
}
// AddNode adds n to the graph. It panics if the added node ID matches an existing node ID.
func (g *UndirectedGraph) AddNode(n graph.Node) {
if _, exists := g.nodes[n.ID()]; exists {
panic(fmt.Sprintf("simple: node ID collision: %d", n.ID()))
}
g.nodes[n.ID()] = n
g.edges[n.ID()] = &sliceEdgeHolder{self: n.ID()}
g.freeIDs.Remove(n.ID())
g.usedIDs.Insert(n.ID())
}
// RemoveNode removes n from the graph, as well as any edges attached to it. If the node
// is not in the graph it is a no-op.
func (g *UndirectedGraph) RemoveNode(n graph.Node) {
if _, ok := g.nodes[n.ID()]; !ok {
return
}
delete(g.nodes, n.ID())
g.edges[n.ID()].Visit(func(neighbor int, edge graph.Edge) {
g.edges[neighbor] = g.edges[neighbor].Delete(n.ID())
})
delete(g.edges, n.ID())
g.freeIDs.Insert(n.ID())
g.usedIDs.Remove(n.ID())
}
// SetEdge adds e, an edge from one node to another. If the nodes do not exist, they are added.
// It will panic if the IDs of the e.From and e.To are equal.
func (g *UndirectedGraph) SetEdge(e graph.Edge) {
var (
from = e.From()
fid = from.ID()
to = e.To()
tid = to.ID()
)
if fid == tid {
panic("simple: adding self edge")
}
if !g.Has(from) {
g.AddNode(from)
}
if !g.Has(to) {
g.AddNode(to)
}
g.edges[fid] = g.edges[fid].Set(tid, e)
g.edges[tid] = g.edges[tid].Set(fid, e)
}
// RemoveEdge removes e from the graph, leaving the terminal nodes. If the edge does not exist
// it is a no-op.
func (g *UndirectedGraph) RemoveEdge(e graph.Edge) {
from, to := e.From(), e.To()
if _, ok := g.nodes[from.ID()]; !ok {
return
}
if _, ok := g.nodes[to.ID()]; !ok {
return
}
g.edges[from.ID()] = g.edges[from.ID()].Delete(to.ID())
g.edges[to.ID()] = g.edges[to.ID()].Delete(from.ID())
}
// Node returns the node in the graph with the given ID.
func (g *UndirectedGraph) Node(id int) graph.Node {
return g.nodes[id]
}
// Has returns whether the node exists within the graph.
func (g *UndirectedGraph) Has(n graph.Node) bool {
_, ok := g.nodes[n.ID()]
return ok
}
// Nodes returns all the nodes in the graph.
func (g *UndirectedGraph) Nodes() []graph.Node {
nodes := make([]graph.Node, len(g.nodes))
i := 0
for _, n := range g.nodes {
nodes[i] = n
i++
}
return nodes
}
// Edges returns all the edges in the graph.
func (g *UndirectedGraph) Edges() []graph.Edge {
var edges []graph.Edge
seen := make(map[[2]int]struct{})
for _, u := range g.edges {
u.Visit(func(neighbor int, e graph.Edge) {
uid := e.From().ID()
vid := e.To().ID()
if _, ok := seen[[2]int{uid, vid}]; ok {
return
}
seen[[2]int{uid, vid}] = struct{}{}
seen[[2]int{vid, uid}] = struct{}{}
edges = append(edges, e)
})
}
return edges
}
// From returns all nodes in g that can be reached directly from n.
func (g *UndirectedGraph) From(n graph.Node) []graph.Node {
if !g.Has(n) {
return nil
}
nodes := make([]graph.Node, g.edges[n.ID()].Len())
i := 0
g.edges[n.ID()].Visit(func(neighbor int, edge graph.Edge) {
nodes[i] = g.nodes[neighbor]
i++
})
return nodes
}
// HasEdgeBetween returns whether an edge exists between nodes x and y.
func (g *UndirectedGraph) HasEdgeBetween(x, y graph.Node) bool {
_, ok := g.edges[x.ID()].Get(y.ID())
return ok
}
// Edge returns the edge from u to v if such an edge exists and nil otherwise.
// The node v must be directly reachable from u as defined by the From method.
func (g *UndirectedGraph) Edge(u, v graph.Node) graph.Edge {
return g.EdgeBetween(u, v)
}
// EdgeBetween returns the edge between nodes x and y.
func (g *UndirectedGraph) EdgeBetween(x, y graph.Node) graph.Edge {
// We don't need to check if neigh exists because
// it's implicit in the edges access.
if !g.Has(x) {
return nil
}
edge, _ := g.edges[x.ID()].Get(y.ID())
return edge
}
// Weight returns the weight for the edge between x and y if Edge(x, y) returns a non-nil Edge.
// If x and y are the same node or there is no joining edge between the two nodes the weight
// value returned is either the graph's absent or self value. Weight returns true if an edge
// exists between x and y or if x and y have the same ID, false otherwise.
func (g *UndirectedGraph) Weight(x, y graph.Node) (w float64, ok bool) {
xid := x.ID()
yid := y.ID()
if xid == yid {
return g.self, true
}
if n, ok := g.edges[xid]; ok {
if e, ok := n.Get(yid); ok {
return e.Weight(), true
}
}
return g.absent, false
}
// Degree returns the degree of n in g.
func (g *UndirectedGraph) Degree(n graph.Node) int {
if _, ok := g.nodes[n.ID()]; !ok {
return 0
}
return g.edges[n.ID()].Len()
}
// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package simple
import (
"math"
"testing"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
)
var _ graph.Graph = (*UndirectedGraph)(nil)
func TestAssertMutableNotDirected(t *testing.T) {
var g graph.UndirectedBuilder = NewUndirectedGraph(0, math.Inf(1))
if _, ok := g.(graph.Directed); ok {
t.Fatal("Graph is directed, but a MutableGraph cannot safely be directed!")
}
}
func TestMaxID(t *testing.T) {
g := NewUndirectedGraph(0, math.Inf(1))
nodes := make(map[graph.Node]struct{})
for i := Node(0); i < 3; i++ {
g.AddNode(i)
nodes[i] = struct{}{}
}
g.RemoveNode(Node(0))
delete(nodes, Node(0))
g.RemoveNode(Node(2))
delete(nodes, Node(2))
n := Node(g.NewNodeID())
g.AddNode(n)
if !g.Has(n) {
t.Error("added node does not exist in graph")
}
if _, exists := nodes[n]; exists {
t.Errorf("Created already existing node id: %v", n.ID())
}
}
// Test for issue #123 https://github.com/gonum/graph/issues/123
func TestIssue123UndirectedGraph(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("unexpected panic: %v", r)
}
}()
g := NewUndirectedGraph(0, math.Inf(1))
n0 := Node(g.NewNodeID())
g.AddNode(n0)
n1 := Node(g.NewNodeID())
g.AddNode(n1)
g.RemoveNode(n0)
n2 := Node(g.NewNodeID())
g.AddNode(n2)
}
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"traverse.go",
"visit_depth_first.go",
],
tags = ["automanaged"],
deps = [
"//third_party/forked/gonum/graph:go_default_library",
"//third_party/forked/gonum/graph/internal/linear:go_default_library",
"//vendor/golang.org/x/tools/container/intsets:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
// Copyright ©2015 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package traverse provides basic graph traversal primitives.
package traverse
import (
"golang.org/x/tools/container/intsets"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
"k8s.io/kubernetes/third_party/forked/gonum/graph/internal/linear"
)
// BreadthFirst implements stateful breadth-first graph traversal.
type BreadthFirst struct {
EdgeFilter func(graph.Edge) bool
Visit func(u, v graph.Node)
queue linear.NodeQueue
visited *intsets.Sparse
}
// Walk performs a breadth-first traversal of the graph g starting from the given node,
// depending on the the EdgeFilter field and the until parameter if they are non-nil. The
// traversal follows edges for which EdgeFilter(edge) is true and returns the first node
// for which until(node, depth) is true. During the traversal, if the Visit field is
// non-nil, it is called with the nodes joined by each followed edge.
func (b *BreadthFirst) Walk(g graph.Graph, from graph.Node, until func(n graph.Node, d int) bool) graph.Node {
if b.visited == nil {
b.visited = &intsets.Sparse{}
}
b.queue.Enqueue(from)
b.visited.Insert(from.ID())
var (
depth int
children int
untilNext = 1
)
for b.queue.Len() > 0 {
t := b.queue.Dequeue()
if until != nil && until(t, depth) {
return t
}
for _, n := range g.From(t) {
if b.EdgeFilter != nil && !b.EdgeFilter(g.Edge(t, n)) {
continue
}
if b.visited.Has(n.ID()) {
continue
}
if b.Visit != nil {
b.Visit(t, n)
}
b.visited.Insert(n.ID())
children++
b.queue.Enqueue(n)
}
if untilNext--; untilNext == 0 {
depth++
untilNext = children
children = 0
}
}
return nil
}
// WalkAll calls Walk for each unvisited node of the graph g using edges independent
// of their direction. The functions before and after are called prior to commencing
// and after completing each walk if they are non-nil respectively. The function
// during is called on each node as it is traversed.
func (b *BreadthFirst) WalkAll(g graph.Undirected, before, after func(), during func(graph.Node)) {
b.Reset()
for _, from := range g.Nodes() {
if b.Visited(from) {
continue
}
if before != nil {
before()
}
b.Walk(g, from, func(n graph.Node, _ int) bool {
if during != nil {
during(n)
}
return false
})
if after != nil {
after()
}
}
}
// Visited returned whether the node n was visited during a traverse.
func (b *BreadthFirst) Visited(n graph.Node) bool {
return b.visited != nil && b.visited.Has(n.ID())
}
// Reset resets the state of the traverser for reuse.
func (b *BreadthFirst) Reset() {
b.queue.Reset()
if b.visited != nil {
b.visited.Clear()
}
}
// DepthFirst implements stateful depth-first graph traversal.
type DepthFirst struct {
EdgeFilter func(graph.Edge) bool
Visit func(u, v graph.Node)
stack linear.NodeStack
visited *intsets.Sparse
}
// Walk performs a depth-first traversal of the graph g starting from the given node,
// depending on the the EdgeFilter field and the until parameter if they are non-nil. The
// traversal follows edges for which EdgeFilter(edge) is true and returns the first node
// for which until(node) is true. During the traversal, if the Visit field is non-nil, it
// is called with the nodes joined by each followed edge.
func (d *DepthFirst) Walk(g graph.Graph, from graph.Node, until func(graph.Node) bool) graph.Node {
if d.visited == nil {
d.visited = &intsets.Sparse{}
}
d.stack.Push(from)
d.visited.Insert(from.ID())
for d.stack.Len() > 0 {
t := d.stack.Pop()
if until != nil && until(t) {
return t
}
for _, n := range g.From(t) {
if d.EdgeFilter != nil && !d.EdgeFilter(g.Edge(t, n)) {
continue
}
if d.visited.Has(n.ID()) {
continue
}
if d.Visit != nil {
d.Visit(t, n)
}
d.visited.Insert(n.ID())
d.stack.Push(n)
}
}
return nil
}
// WalkAll calls Walk for each unvisited node of the graph g using edges independent
// of their direction. The functions before and after are called prior to commencing
// and after completing each walk if they are non-nil respectively. The function
// during is called on each node as it is traversed.
func (d *DepthFirst) WalkAll(g graph.Undirected, before, after func(), during func(graph.Node)) {
d.Reset()
for _, from := range g.Nodes() {
if d.Visited(from) {
continue
}
if before != nil {
before()
}
d.Walk(g, from, func(n graph.Node) bool {
if during != nil {
during(n)
}
return false
})
if after != nil {
after()
}
}
}
// Visited returned whether the node n was visited during a traverse.
func (d *DepthFirst) Visited(n graph.Node) bool {
return d.visited != nil && d.visited.Has(n.ID())
}
// Reset resets the state of the traverser for reuse.
func (d *DepthFirst) Reset() {
d.stack = d.stack[:0]
if d.visited != nil {
d.visited.Clear()
}
}
// Copyright ©2015 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package traverse provides basic graph traversal primitives.
package traverse
import (
"golang.org/x/tools/container/intsets"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
"k8s.io/kubernetes/third_party/forked/gonum/graph/internal/linear"
)
// VisitableGraph
type VisitableGraph interface {
graph.Graph
// VisitFrom invokes visitor with all nodes that can be reached directly from the given node.
// If visitor returns false, visiting is short-circuited.
VisitFrom(from graph.Node, visitor func(graph.Node) (shouldContinue bool))
}
// VisitingDepthFirst implements stateful depth-first graph traversal on a visitable graph.
type VisitingDepthFirst struct {
EdgeFilter func(graph.Edge) bool
Visit func(u, v graph.Node)
stack linear.NodeStack
visited *intsets.Sparse
}
// Walk performs a depth-first traversal of the graph g starting from the given node,
// depending on the the EdgeFilter field and the until parameter if they are non-nil. The
// traversal follows edges for which EdgeFilter(edge) is true and returns the first node
// for which until(node) is true. During the traversal, if the Visit field is non-nil, it
// is called with the nodes joined by each followed edge.
func (d *VisitingDepthFirst) Walk(g VisitableGraph, from graph.Node, until func(graph.Node) bool) graph.Node {
if d.visited == nil {
d.visited = &intsets.Sparse{}
}
d.stack.Push(from)
d.visited.Insert(from.ID())
if until != nil && until(from) {
return from
}
var found graph.Node
for d.stack.Len() > 0 {
t := d.stack.Pop()
g.VisitFrom(t, func(n graph.Node) (shouldContinue bool) {
if d.EdgeFilter != nil && !d.EdgeFilter(g.Edge(t, n)) {
return true
}
if d.visited.Has(n.ID()) {
return true
}
if d.Visit != nil {
d.Visit(t, n)
}
d.visited.Insert(n.ID())
d.stack.Push(n)
if until != nil && until(n) {
found = n
return false
}
return true
})
if found != nil {
return found
}
}
return nil
}
// Visited returned whether the node n was visited during a traverse.
func (d *VisitingDepthFirst) Visited(n graph.Node) bool {
return d.visited != nil && d.visited.Has(n.ID())
}
// Reset resets the state of the traverser for reuse.
func (d *VisitingDepthFirst) Reset() {
d.stack = d.stack[:0]
if d.visited != nil {
d.visited.Clear()
}
}
......@@ -346,6 +346,7 @@ filegroup(
"//vendor/golang.org/x/text/unicode/norm:all-srcs",
"//vendor/golang.org/x/text/width:all-srcs",
"//vendor/golang.org/x/time/rate:all-srcs",
"//vendor/golang.org/x/tools/container/intsets:all-srcs",
"//vendor/google.golang.org/api/cloudmonitoring/v2beta2:all-srcs",
"//vendor/google.golang.org/api/compute/v0.alpha:all-srcs",
"//vendor/google.golang.org/api/compute/v0.beta:all-srcs",
......
# This source code refers to The Go Authors for copyright purposes.
# The master list of authors is in the main Go distribution,
# visible at http://tip.golang.org/AUTHORS.
# This source code was written by the Go contributors.
# The master list of contributors is in the main Go distribution,
# visible at http://tip.golang.org/CONTRIBUTORS.
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"popcnt_amd64.go",
"popcnt_amd64.s",
"sparse.go",
"util.go",
],
tags = ["automanaged"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build amd64,!appengine,!gccgo
package intsets
func popcnt(x word) int
func havePOPCNT() bool
var hasPOPCNT = havePOPCNT()
// popcount returns the population count (number of set bits) of x.
func popcount(x word) int {
if hasPOPCNT {
return popcnt(x)
}
return popcountTable(x) // faster than Hacker's Delight
}
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build amd64,!appengine,!gccgo
#include "textflag.h"
// func havePOPCNT() bool
TEXT ·havePOPCNT(SB),4,$0
MOVQ $1, AX
CPUID
SHRQ $23, CX
ANDQ $1, CX
MOVB CX, ret+0(FP)
RET
// func popcnt(word) int
TEXT ·popcnt(SB),NOSPLIT,$0-8
XORQ AX, AX
MOVQ x+0(FP), SI
// POPCNT (SI), AX is not recognized by Go assembler,
// so we assemble it ourselves.
BYTE $0xf3
BYTE $0x48
BYTE $0x0f
BYTE $0xb8
BYTE $0xc6
MOVQ AX, ret+8(FP)
RET
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build gccgo
package intsets
func popcount(x word) int
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build gccgo
#include <errno.h>
#include <stdint.h>
#include <unistd.h>
#define _STRINGIFY2_(x) #x
#define _STRINGIFY_(x) _STRINGIFY2_(x)
#define GOSYM_PREFIX _STRINGIFY_(__USER_LABEL_PREFIX__)
extern intptr_t popcount(uintptr_t x) __asm__(GOSYM_PREFIX GOPKGPATH ".popcount");
intptr_t popcount(uintptr_t x) {
return __builtin_popcountl((unsigned long)(x));
}
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !amd64 appengine
// +build !gccgo
package intsets
import "runtime"
// We compared three algorithms---Hacker's Delight, table lookup,
// and AMD64's SSE4.1 hardware POPCNT---on a 2.67GHz Xeon X5550.
//
// % GOARCH=amd64 go test -run=NONE -bench=Popcount
// POPCNT 5.12 ns/op
// Table 8.53 ns/op
// HackersDelight 9.96 ns/op
//
// % GOARCH=386 go test -run=NONE -bench=Popcount
// Table 10.4 ns/op
// HackersDelight 5.23 ns/op
//
// (AMD64's ABM1 hardware supports ntz and nlz too,
// but they aren't critical.)
// popcount returns the population count (number of set bits) of x.
func popcount(x word) int {
if runtime.GOARCH == "386" {
return popcountHD(uint32(x))
}
return popcountTable(x)
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package intsets
// From Hacker's Delight, fig 5.2.
func popcountHD(x uint32) int {
x -= (x >> 1) & 0x55555555
x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
x = (x + (x >> 4)) & 0x0f0f0f0f
x = x + (x >> 8)
x = x + (x >> 16)
return int(x & 0x0000003f)
}
var a [1 << 8]byte
func init() {
for i := range a {
var n byte
for x := i; x != 0; x >>= 1 {
if x&1 != 0 {
n++
}
}
a[i] = n
}
}
func popcountTable(x word) int {
return int(a[byte(x>>(0*8))] +
a[byte(x>>(1*8))] +
a[byte(x>>(2*8))] +
a[byte(x>>(3*8))] +
a[byte(x>>(4*8))] +
a[byte(x>>(5*8))] +
a[byte(x>>(6*8))] +
a[byte(x>>(7*8))])
}
// nlz returns the number of leading zeros of x.
// From Hacker's Delight, fig 5.11.
func nlz(x word) int {
x |= (x >> 1)
x |= (x >> 2)
x |= (x >> 4)
x |= (x >> 8)
x |= (x >> 16)
x |= (x >> 32)
return popcount(^x)
}
// ntz returns the number of trailing zeros of x.
// From Hacker's Delight, fig 5.13.
func ntz(x word) int {
if x == 0 {
return bitsPerWord
}
n := 1
if bitsPerWord == 64 {
if (x & 0xffffffff) == 0 {
n = n + 32
x = x >> 32
}
}
if (x & 0x0000ffff) == 0 {
n = n + 16
x = x >> 16
}
if (x & 0x000000ff) == 0 {
n = n + 8
x = x >> 8
}
if (x & 0x0000000f) == 0 {
n = n + 4
x = x >> 4
}
if (x & 0x00000003) == 0 {
n = n + 2
x = x >> 2
}
return n - int(x&1)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment