Skip to content

Commit

Permalink
Merge pull request #2148 from pravisankar/project-nodeenv
Browse files Browse the repository at this point in the history
Merged by openshift-bot
  • Loading branch information
OpenShift Bot committed May 12, 2015
2 parents 43e6a00 + abd33ed commit 34c57a9
Show file tree
Hide file tree
Showing 22 changed files with 997 additions and 75 deletions.
9 changes: 6 additions & 3 deletions pkg/cmd/admin/project/new_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import (
const NewProjectRecommendedName = "new-project"

type NewProjectOptions struct {
ProjectName string
DisplayName string
Description string
ProjectName string
DisplayName string
Description string
NodeSelector string

Client client.Interface

Expand Down Expand Up @@ -56,6 +57,7 @@ func NewCmdNewProject(name, fullName string, f *clientcmd.Factory, out io.Writer
cmd.Flags().StringVar(&options.AdminUser, "admin", "", "project admin username")
cmd.Flags().StringVar(&options.DisplayName, "display-name", "", "project display name")
cmd.Flags().StringVar(&options.Description, "description", "", "project description")
cmd.Flags().StringVar(&options.NodeSelector, "node-selector", "", "Restrict pods onto nodes matching given label selector. Format: '<key1>=<value1>, <key2>=<value2>...'")

return cmd
}
Expand Down Expand Up @@ -83,6 +85,7 @@ func (o *NewProjectOptions) Run() error {
project.Annotations = make(map[string]string)
project.Annotations["description"] = o.Description
project.Annotations["displayName"] = o.DisplayName
project.Annotations["openshift.io/node-selector"] = o.NodeSelector
project, err := o.Client.Projects().Create(project)
if err != nil {
return err
Expand Down
15 changes: 9 additions & 6 deletions pkg/cmd/cli/cmd/request_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ import (
)

type NewProjectOptions struct {
ProjectName string
DisplayName string
Description string
ProjectName string
DisplayName string
Description string
NodeSelector string

Client client.Interface

Expand All @@ -40,16 +41,16 @@ After your project is created you can switch to it using %[2]s <project name>.`
requestProject_example = ` // Create a new project with minimal information
$ %[1]s web-team-dev
// Create a new project with a description
$ %[1]s web-team-dev --display-name="Web Team Development" --description="Development project for the web team."`
// Create a new project with a description and node selector
$ %[1]s web-team-dev --display-name="Web Team Development" --description="Development project for the web team." --node-selector="env=dev"`
)

func NewCmdRequestProject(name, fullName, oscLoginName, oscProjectName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
options := &NewProjectOptions{}
options.Out = out

cmd := &cobra.Command{
Use: fmt.Sprintf("%s NAME [--display-name=DISPLAYNAME] [--description=DESCRIPTION]", name),
Use: fmt.Sprintf("%s NAME [--display-name=DISPLAYNAME] [--description=DESCRIPTION] [--node-selector=<label selector>]", name),
Short: "Request a new project",
Long: fmt.Sprintf(requestProject_long, oscLoginName, oscProjectName),
Example: fmt.Sprintf(requestProject_example, fullName),
Expand All @@ -71,6 +72,7 @@ func NewCmdRequestProject(name, fullName, oscLoginName, oscProjectName string, f

cmd.Flags().StringVar(&options.DisplayName, "display-name", "", "project display name")
cmd.Flags().StringVar(&options.Description, "description", "", "project description")
cmd.Flags().StringVar(&options.NodeSelector, "node-selector", "", "Restrict pods onto nodes matching given label selector. Format: '<key1>=<value1>, <key2>=<value2>...'")

return cmd
}
Expand Down Expand Up @@ -105,6 +107,7 @@ func (o *NewProjectOptions) Run() error {
projectRequest.DisplayName = o.DisplayName
projectRequest.Annotations = make(map[string]string)
projectRequest.Annotations["description"] = o.Description
projectRequest.Annotations["openshift.io/node-selector"] = o.NodeSelector

project, err := o.Client.ProjectRequests().Create(projectRequest)
if err != nil {
Expand Down
7 changes: 7 additions & 0 deletions pkg/cmd/cli/describe/describer.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,11 +454,18 @@ func (d *ProjectDescriber) Describe(namespace, name string) (string, error) {
if err != nil {
return "", err
}
nodeSelector := ""
if len(project.ObjectMeta.Annotations) > 0 {
if ns, ok := project.ObjectMeta.Annotations["openshift.io/node-selector"]; ok {
nodeSelector = ns
}
}

return tabbedString(func(out *tabwriter.Writer) error {
formatMeta(out, project.ObjectMeta)
formatString(out, "Display Name", project.Annotations["displayName"])
formatString(out, "Status", project.Status.Phase)
formatString(out, "Node Selector", nodeSelector)
return nil
})
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/server/api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ func GetMasterFileReferences(config *MasterConfig) []*string {

refs = append(refs, &config.PolicyConfig.BootstrapPolicyFile)

refs = append(refs, &config.ProjectNodeSelector)

return refs
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/server/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ type MasterConfig struct {
// PolicyConfig holds information about where to locate critical pieces of bootstrapping policy
PolicyConfig PolicyConfig

// ProjectNodeSelector holds default project node label selector
ProjectNodeSelector string `json:"projectNodeSelector,omitempty"`
// ProjectRequestConfig holds information about how to handle new project requests
ProjectRequestConfig ProjectRequestConfig
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/cmd/server/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ type MasterConfig struct {

PolicyConfig PolicyConfig `json:"policyConfig"`

// ProjectNodeSelector holds default project node label selector
ProjectNodeSelector string `json:"projectNodeSelector,omitempty"`
// ProjectRequestConfig holds information about how to handle new project requests
ProjectRequestConfig ProjectRequestConfig `json:"projectRequestConfig"`
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/cmd/server/api/validation/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"

"github.com/openshift/origin/pkg/cmd/server/api"
"github.com/openshift/origin/pkg/util/labelselector"
)

func ValidateMasterConfig(config *api.MasterConfig) fielderrors.ValidationErrorList {
Expand Down Expand Up @@ -88,6 +89,8 @@ func ValidateMasterConfig(config *api.MasterConfig) fielderrors.ValidationErrorL
allErrs = append(allErrs, ValidateOAuthConfig(config.OAuthConfig).Prefix("oauthConfig")...)
}

allErrs = append(allErrs, ValidateProjectNodeSelector(config.ProjectNodeSelector)...)

allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...)

allErrs = append(allErrs, ValidateProjectRequestConfig(config.ProjectRequestConfig).Prefix("projectRequestConfig")...)
Expand Down Expand Up @@ -115,6 +118,19 @@ func ValidateEtcdStorageConfig(config api.EtcdStorageConfig) fielderrors.Validat
return allErrs
}

func ValidateProjectNodeSelector(nodeSelector string) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}

if len(nodeSelector) > 0 {
_, err := labelselector.Parse(nodeSelector)
if err != nil {
allErrs = append(allErrs, fielderrors.NewFieldInvalid("projectNodeSelector", nodeSelector, "must be a valid label selector"))
}
}

return allErrs
}

func ValidateAssetConfig(config *api.AssetConfig) fielderrors.ValidationErrorList {
allErrs := fielderrors.ValidationErrorList{}

Expand Down
3 changes: 2 additions & 1 deletion pkg/cmd/server/kubernetes/master_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ func BuildKubernetesMasterConfig(options configapi.MasterConfig, requestContextM
portalNet := net.IPNet(flagtypes.DefaultIPNet(options.KubernetesMasterConfig.ServicesSubnet))

// in-order list of plug-ins that should intercept admission decisions
admissionControlPluginNames := []string{"NamespaceExists", "NamespaceLifecycle", "LimitRanger", "ResourceQuota"}
// TODO: Push node environment support to upstream in future
admissionControlPluginNames := []string{"NamespaceExists", "NamespaceLifecycle", "OriginPodNodeEnvironment", "LimitRanger", "ResourceQuota"}
admissionController := admission.NewFromPlugins(kubeClient, admissionControlPluginNames, "")

_, portString, err := net.SplitHostPort(options.ServingInfo.BindAddress)
Expand Down
7 changes: 7 additions & 0 deletions pkg/cmd/server/origin/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import (
clientetcd "github.com/openshift/origin/pkg/oauth/registry/oauthclient/etcd"
clientauthetcd "github.com/openshift/origin/pkg/oauth/registry/oauthclientauthorization/etcd"
projectapi "github.com/openshift/origin/pkg/project/api"
projectcache "github.com/openshift/origin/pkg/project/cache"
projectcontroller "github.com/openshift/origin/pkg/project/controller"
projectproxy "github.com/openshift/origin/pkg/project/registry/project/proxy"
projectrequeststorage "github.com/openshift/origin/pkg/project/registry/projectrequest/delegated"
Expand Down Expand Up @@ -768,6 +769,12 @@ func (c *MasterConfig) RunDNSServer() {
glog.Infof("OpenShift DNS listening at %s", c.Options.DNSConfig.BindAddress)
}

// RunProjectCache populates project cache, used by scheduler and project admission controller.
func (c *MasterConfig) RunProjectCache() {
glog.Infof("Using default project node label selector: %s", c.Options.ProjectNodeSelector)
projectcache.RunProjectCache(c.PrivilegedLoopbackKubernetesClient, c.Options.ProjectNodeSelector)
}

// RunBuildController starts the build sync loop for builds and buildConfig processing.
func (c *MasterConfig) RunBuildController() {
// initialize build controller
Expand Down
6 changes: 6 additions & 0 deletions pkg/cmd/server/start/master_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type MasterArgs struct {
KubeConnectionArgs *KubeConnectionArgs

SchedulerConfigFile string
ProjectNodeSelector string
}

// BindMasterArgs binds the options to the flags with prefix + default flag names
Expand All @@ -60,6 +61,7 @@ func BindMasterArgs(args *MasterArgs, flags *pflag.FlagSet, prefix string) {

flags.Var(&args.NodeList, prefix+"nodes", "The hostnames of each node. This currently must be specified up front. Comma delimited list")
flags.Var(&args.CORSAllowedOrigins, prefix+"cors-allowed-origins", "List of allowed origins for CORS, comma separated. An allowed origin can be a regular expression to support subdomain matching. CORS is enabled for localhost, 127.0.0.1, and the asset server by default.")
flags.StringVar(&args.ProjectNodeSelector, prefix+"project-node-selector", "", "Default node label selector for the project if not explicitly specified. Format: '<key1>=<value1>, <key2>=<value2...'")
}

// NewDefaultMasterArgs creates MasterArgs with sub-objects created and default values set.
Expand Down Expand Up @@ -197,6 +199,10 @@ func (args MasterArgs) BuildSerializeableMasterConfig() (*configapi.MasterConfig
},
}

if len(args.ProjectNodeSelector) > 0 {
config.ProjectNodeSelector = args.ProjectNodeSelector
}

if args.ListenArg.UseTLS() {
config.ServingInfo.ServerCert = admin.DefaultMasterServingCertInfo(args.ConfigDir.Value())
config.ServingInfo.ClientCA = admin.DefaultAPIClientCAFile(args.ConfigDir.Value())
Expand Down
3 changes: 2 additions & 1 deletion pkg/cmd/server/start/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ import (
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/exists"
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/namespace/lifecycle"
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/resourcequota"
_ "github.com/openshift/origin/pkg/project/admission"
_ "github.com/openshift/origin/pkg/project/admission/lifecycle"
_ "github.com/openshift/origin/pkg/project/admission/nodeenv"
)
3 changes: 2 additions & 1 deletion pkg/cmd/server/start/start_master.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,9 @@ func StartMaster(openshiftMasterConfig *configapi.MasterConfig) error {
if err != nil {
return err
}
// must start policy caching immediately
// Must start policy caching immediately
openshiftConfig.RunPolicyCache()
openshiftConfig.RunProjectCache()

unprotectedInstallers := []origin.APIInstaller{}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,20 @@ import (
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"

"github.com/openshift/origin/pkg/api/latest"
"github.com/openshift/origin/pkg/project/cache"
)

// TODO: modify the upstream plug-in so this can be collapsed
// need ability to specify a RESTMapper on upstream version
func init() {
admission.RegisterPlugin("OriginNamespaceLifecycle", func(client client.Interface, config io.Reader) (admission.Interface, error) {
return NewLifecycle(client), nil
return NewLifecycle()
})
}

type lifecycle struct {
client client.Interface
store cache.Store
}

// Admit enforces that a namespace must exist in order to associate content with it.
Expand All @@ -66,35 +61,19 @@ func (e *lifecycle) Admit(a admission.Attributes) (err error) {
return nil
}

// check for namespace in the cache
namespaceObj, exists, err := e.store.Get(&kapi.Namespace{
ObjectMeta: kapi.ObjectMeta{
Name: a.GetNamespace(),
Namespace: "",
},
Status: kapi.NamespaceStatus{},
})

if err != nil {
return err
}

name := "Unknown"
obj := a.GetObject()
if obj != nil {
name, _ = meta.NewAccessor().Name(obj)
}

var namespace *kapi.Namespace
if exists {
namespace = namespaceObj.(*kapi.Namespace)
} else {
// Our watch maybe latent, so we make a best effort to get the object, and only fail if not found
namespace, err = e.client.Namespaces().Get(a.GetNamespace())
// the namespace does not exist, so prevent create and update in that namespace
if err != nil {
return apierrors.NewForbidden(kind, name, fmt.Errorf("Namespace %s does not exist", a.GetNamespace()))
}
projects, err := cache.GetProjectCache()
if err != nil {
return err
}
namespace, err := projects.GetNamespaceObject(a.GetNamespace())
if err != nil {
return apierrors.NewForbidden(kind, name, err)
}

if a.GetOperation() != "CREATE" {
Expand All @@ -108,24 +87,6 @@ func (e *lifecycle) Admit(a admission.Attributes) (err error) {
return apierrors.NewForbidden(kind, name, fmt.Errorf("Namespace %s is terminating", a.GetNamespace()))
}

func NewLifecycle(c client.Interface) admission.Interface {
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
reflector := cache.NewReflector(
&cache.ListWatch{
ListFunc: func() (runtime.Object, error) {
return c.Namespaces().List(labels.Everything(), fields.Everything())
},
WatchFunc: func(resourceVersion string) (watch.Interface, error) {
return c.Namespaces().Watch(labels.Everything(), fields.Everything(), resourceVersion)
},
},
&kapi.Namespace{},
store,
0,
)
reflector.Run()
return &lifecycle{
client: c,
store: store,
}
func NewLifecycle() (admission.Interface, error) {
return &lifecycle{}, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,16 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient"

buildapi "github.com/openshift/origin/pkg/build/api"
projectcache "github.com/openshift/origin/pkg/project/cache"
)

// TestAdmissionExists verifies you cannot create Origin content if namespace is not known
func TestAdmissionExists(t *testing.T) {
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
mockClient := &testclient.Fake{
Err: fmt.Errorf("DOES NOT EXIST"),
}
handler := &lifecycle{
client: mockClient,
store: store,
}
projectcache.FakeProjectCache(mockClient, cache.NewStore(cache.MetaNamespaceKeyFunc), "")
handler := &lifecycle{}
build := &buildapi.Build{
ObjectMeta: kapi.ObjectMeta{Name: "buildid"},
Parameters: buildapi.BuildParameters{
Expand Down Expand Up @@ -62,10 +60,8 @@ func TestAdmissionLifecycle(t *testing.T) {
store := cache.NewStore(cache.MetaNamespaceIndexFunc)
store.Add(namespaceObj)
mockClient := &testclient.Fake{}
handler := &lifecycle{
client: mockClient,
store: store,
}
projectcache.FakeProjectCache(mockClient, store, "")
handler := &lifecycle{}
build := &buildapi.Build{
ObjectMeta: kapi.ObjectMeta{Name: "buildid"},
Parameters: buildapi.BuildParameters{
Expand Down

0 comments on commit 34c57a9

Please sign in to comment.