{"action":"created","comment":{"url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments/45209716","id":45209716,"diff_hunk":"@@ -50,77 +62,212 @@ type AllOrNothingProcurement []Procurement\n \n // Procure runs each Procurement in the receiver list. The first Procurement func that\n // fails triggers T.Reset() and the error is returned, otherwise returns nil.\n-func (a AllOrNothingProcurement) Procure(t *T, offer *mesos.Offer) error {\n+func (a AllOrNothingProcurement) Procure(t *T, offer *mesos.Offer, n *api.Node, spec *Spec) error {\n \tfor _, p := range a {\n-\t\tif err := p(t, offer); err != nil {\n-\t\t\tt.Reset()\n+\t\terr := p.Procure(t, offer, n, spec)\n+\t\tif err != nil {\n \t\t\treturn err\n \t\t}\n \t}\n \treturn nil\n }\n \n-// ValidateProcurement checks that the offered resources are kosher, and if not panics.\n+// NewValidateProcurement checks that the offered resources are kosher, and if not panics.\n // If things check out ok, t.Spec is cleared and nil is returned.\n-func ValidateProcurement(t *T, offer *mesos.Offer) error {\n-\tif offer == nil {\n-\t\t//programming error\n-\t\tpanic(\"offer details are nil\")\n-\t}\n-\tt.Spec = Spec{}\n-\treturn nil\n+func NewValidateProcurement() Procurement {\n+\treturn ProcurementFunc(func(t *T, offer *mesos.Offer, _ *api.Node, spec *Spec) error {\n+\t\tif offer == nil {\n+\t\t\t//programming error\n+\t\t\tpanic(\"offer details are nil\")\n+\t\t}\n+\t\treturn nil\n+\t})\n }\n \n-// NodeProcurement updates t.Spec in preparation for the task to be launched on the\n+// NewNodeProcurement returns a Procurement updating t.Spec in preparation for the task to be launched on the\n // slave associated with the offer.\n-func NodeProcurement(t *T, offer *mesos.Offer) error {\n-\tt.Spec.SlaveID = offer.GetSlaveId().GetValue()\n-\tt.Spec.AssignedSlave = offer.GetHostname()\n-\treturn nil\n+func NewNodeProcurement() Procurement {\n+\treturn ProcurementFunc(func(t *T, offer *mesos.Offer, n *api.Node, spec *Spec) error {\n+\t\t// if the user has specified a target host, make sure this offer is for that host\n+\t\tif t.Pod.Spec.NodeName != \"\" && offer.GetHostname() != t.Pod.Spec.NodeName {\n+\t\t\treturn fmt.Errorf(\n+\t\t\t\t\"NodeName %q does not match offer hostname %q\",\n+\t\t\t\tt.Pod.Spec.NodeName, offer.GetHostname(),\n+\t\t\t)\n+\t\t}\n+\n+\t\t// check the NodeSelector\n+\t\tif len(t.Pod.Spec.NodeSelector) > 0 {\n+\t\t\tif n.Labels == nil {\n+\t\t\t\treturn fmt.Errorf(\n+\t\t\t\t\t\"NodeSelector %v does not match empty labels of pod %s/%s\",\n+\t\t\t\t\tt.Pod.Spec.NodeSelector, t.Pod.Namespace, t.Pod.Name,\n+\t\t\t\t)\n+\t\t\t}\n+\t\t\tselector := labels.SelectorFromSet(t.Pod.Spec.NodeSelector)\n+\t\t\tif !selector.Matches(labels.Set(n.Labels)) {\n+\t\t\t\treturn fmt.Errorf(\n+\t\t\t\t\t\"NodeSelector %v does not match labels %v of pod %s/%s\",\n+\t\t\t\t\tt.Pod.Spec.NodeSelector, t.Pod.Labels, t.Pod.Namespace, t.Pod.Name,\n+\t\t\t\t)\n+\t\t\t}\n+\t\t}\n+\n+\t\tspec.SlaveID = offer.GetSlaveId().GetValue()\n+\t\tspec.AssignedSlave = offer.GetHostname()\n+\n+\t\treturn nil\n+\t})\n+}\n+\n+// NewPodResourcesProcurement converts k8s pod cpu and memory resource requirements into\n+// mesos resource allocations.\n+func NewPodResourcesProcurement() Procurement {\n+\treturn ProcurementFunc(func(t *T, offer *mesos.Offer, _ *api.Node, spec *Spec) error {\n+\t\t// TODO(sttts): fall back to requested resources if resource limit cannot be fulfilled by the offer\n+\t\t_, limits, err := api.PodRequestsAndLimits(&t.Pod)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\n+\t\twantedCpus := float64(mresource.NewCPUShares(limits[api.ResourceCPU]))\n+\t\twantedMem := float64(mresource.NewMegaBytes(limits[api.ResourceMemory]))\n+\n+\t\tlog.V(4).Infof(\n+\t\t\t\"trying to match offer with pod %v/%v: cpus: %.2f mem: %.2f MB\",\n+\t\t\tt.Pod.Namespace, t.Pod.Name, wantedCpus, wantedMem,\n+\t\t)\n+\n+\t\tpodRoles := t.Roles()\n+\t\tprocuredCpu, remaining := procureRoleResources(\"cpus\", wantedCpus, podRoles, offer.GetResources())\n+\t\tif procuredCpu == nil {\n+\t\t\treturn fmt.Errorf(\n+\t\t\t\t\"not enough cpu resources for pod %s/%s: want=%v\",\n+\t\t\t\tt.Pod.Namespace, t.Pod.Name, wantedCpus,\n+\t\t\t)\n+\t\t}\n+\n+\t\tprocuredMem, remaining := procureRoleResources(\"mem\", wantedMem, podRoles, remaining)\n+\t\tif procuredMem == nil {\n+\t\t\treturn fmt.Errorf(\n+\t\t\t\t\"not enough mem resources for pod %s/%s: want=%v\",\n+\t\t\t\tt.Pod.Namespace, t.Pod.Name, wantedMem,\n+\t\t\t)\n+\t\t}\n+\n+\t\tspec.Resources = append(spec.Resources, append(procuredCpu, procuredMem...)...)\n+\t\treturn nil\n+\t})\n }\n \n-type RequirePodResources struct {\n-\tdefaultContainerCPULimit mresource.CPUShares\n-\tdefaultContainerMemLimit mresource.MegaBytes\n+// NewPortsProcurement returns a Procurement procuring ports\n+func NewPortsProcurement() Procurement {\n+\treturn ProcurementFunc(func(t *T, offer *mesos.Offer, _ *api.Node, spec *Spec) error {\n+\t\t// fill in port mapping\n+\t\tif mapping, err := t.mapper.Map(t, offer); err != nil {\n+\t\t\treturn err\n+\t\t} else {\n+\t\t\tports := []Port{}\n+\t\t\tfor _, entry := range mapping {\n+\t\t\t\tports = append(ports, Port{\n+\t\t\t\t\tPort: entry.OfferPort,\n+\t\t\t\t\tRole: entry.Role,\n+\t\t\t\t})\n+\t\t\t}\n+\t\t\tspec.PortMap = mapping\n+\t\t\tspec.Resources = append(spec.Resources, portRangeResources(ports)...)\n+\t\t}\n+\t\treturn nil\n+\t})\n }\n \n-func (r *RequirePodResources) Procure(t *T, offer *mesos.Offer) error {\n-\t// write resource limits into the pod spec which is transferred to the executor. From here\n-\t// on we can expect that the pod spec of a task has proper limits for CPU and memory.\n-\t// TODO(sttts): For a later separation of the kubelet and the executor also patch the pod on the apiserver\n-\t// TODO(sttts): fall back to requested resources if resource limit cannot be fulfilled by the offer\n-\t// TODO(jdef): changing the state of t.Pod here feels dirty, especially since we don't use a kosher\n-\t// method to clone the api.Pod state in T.Clone(). This needs some love.\n-\t_, cpuLimit, _, err := mresource.LimitPodCPU(&t.Pod, r.defaultContainerCPULimit)\n-\tif err != nil {\n-\t\treturn err\n-\t}\n+// NewExecutorResourceProcurer returns a Procurement procuring executor resources\n+// If a given offer has no executor IDs set, the given prototype executor resources are considered for procurement.\n+// If a given offer has one executor ID set, only pod resources are being procured.\n+// An offer with more than one executor ID implies an invariant violation and the first executor ID is being considered.\n+func NewExecutorResourceProcurer(resources []*mesos.Resource, registry executorinfo.Registry) Procurement {\n+\treturn ProcurementFunc(func(t *T, offer *mesos.Offer, _ *api.Node, spec *Spec) error {\n+\t\teids := len(offer.GetExecutorIds())\n+\t\tswitch {\n+\t\tcase eids == 0:\n+\t\t\twantedCpus := sumResources(filterResources(resources, isScalar, hasName(\"cpus\")))\n+\t\t\twantedMem := sumResources(filterResources(resources, isScalar, hasName(\"mem\")))\n \n-\t_, memLimit, _, err := mresource.LimitPodMem(&t.Pod, r.defaultContainerMemLimit)\n-\tif err != nil {\n-\t\treturn err\n-\t}\n+\t\t\tprocuredCpu, remaining := procureRoleResources(\"cpus\", wantedCpus, t.allowedRoles, offer.GetResources())\n+\t\t\tif procuredCpu == nil {\n+\t\t\t\treturn fmt.Errorf(\"not enough cpu resources for executor: want=%v\", wantedCpus)\n+\t\t\t}\n \n-\tlog.V(3).Infof(\"Recording offer(s) %s/%s against pod %v: cpu: %.2f, mem: %.2f MB\", offer.Id, t.Pod.Namespace, t.Pod.Name, cpuLimit, memLimit)\n+\t\t\tprocuredMem, remaining := procureRoleResources(\"mem\", wantedMem, t.allowedRoles, remaining)\n+\t\t\tif procuredMem == nil {\n+\t\t\t\treturn fmt.Errorf(\"not enough mem resources for executor: want=%v\", wantedMem)\n+\t\t\t}\n \n-\tt.Spec.CPU = cpuLimit\n-\tt.Spec.Memory = memLimit\n+\t\t\toffer.Resources = remaining\n+\t\t\tspec.Executor = registry.New(offer.GetHostname(), append(procuredCpu, procuredMem...))\n+\t\t\treturn nil\n \n-\treturn nil\n+\t\tcase eids > 1:\n+\t\t\tlog.Errorf(\"got %d executor IDs in offer, want 1 (procuring first)\", eids)\n+\t\t\tfallthrough\n+\n+\t\tdefault: // eids >= 1\n+\t\t\t// read 1st executor id executor info from cache/attributes/...\n+\t\t\teid := offer.GetExecutorIds()[0].GetValue()\n+\t\t\tlog.V(5).Infof(\"found executor id %q\", eid)\n+\n+\t\t\te, err := registry.Get(offer.GetHostname(), eid)\n+\t\t\tif err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\n+\t\t\tspec.Executor = e\n+\t\t\treturn nil\n+\t\t}\n+\t})\n }\n \n-// PortsProcurement convert host port mappings into mesos port resource allocations.\n-func PortsProcurement(t *T, offer *mesos.Offer) error {\n-\t// fill in port mapping\n-\tif mapping, err := t.mapper.Generate(t, offer); err != nil {\n-\t\treturn err\n-\t} else {\n-\t\tports := []uint64{}\n-\t\tfor _, entry := range mapping {\n-\t\t\tports = append(ports, entry.OfferPort)\n+// smallest number such that 1.0 + epsilon != 1.0\n+// see https://github.com/golang/go/issues/966\n+var epsilon = math.Nextafter(1, 2) - 1\n+\n+// procureRoleResources procures offered resources that\n+// 1. Match the given name\n+// 2. Match the given roles\n+// 3. The given wanted scalar value can be fully consumed by offered resources\n+// Roles are being considered in the specified roles slice ordering.\n+func procureRoleResources(name string, want float64, roles []string, offered []*mesos.Resource) (procured, remaining []*mesos.Resource) {","path":"contrib/mesos/pkg/scheduler/podtask/procurement.go","position":null,"original_position":281,"commit_id":"538256d864262a0a21440fa5caa6121bf4215673","original_commit_id":"560e46ad3e8d5664184e1dc3eaedf0dce74b15bb","user":{"login":"s-urbaniak","id":375856,"avatar_url":"https://avatars.githubusercontent.com/u/375856?v=3","gravatar_id":"","url":"https://api.github.com/users/s-urbaniak","html_url":"https://github.com/s-urbaniak","followers_url":"https://api.github.com/users/s-urbaniak/followers","following_url":"https://api.github.com/users/s-urbaniak/following{/other_user}","gists_url":"https://api.github.com/users/s-urbaniak/gists{/gist_id}","starred_url":"https://api.github.com/users/s-urbaniak/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/s-urbaniak/subscriptions","organizations_url":"https://api.github.com/users/s-urbaniak/orgs","repos_url":"https://api.github.com/users/s-urbaniak/repos","events_url":"https://api.github.com/users/s-urbaniak/events{/privacy}","received_events_url":"https://api.github.com/users/s-urbaniak/received_events","type":"User","site_admin":false},"body":"done","created_at":"2015-11-18T15:05:07Z","updated_at":"2015-11-18T15:05:07Z","html_url":"https://github.com/kubernetes/kubernetes/pull/15775#discussion_r45209716","pull_request_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/15775","_links":{"self":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments/45209716"},"html":{"href":"https://github.com/kubernetes/kubernetes/pull/15775#discussion_r45209716"},"pull_request":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/15775"}}},"pull_request":{"url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/15775","id":47901435,"html_url":"https://github.com/kubernetes/kubernetes/pull/15775","diff_url":"https://github.com/kubernetes/kubernetes/pull/15775.diff","patch_url":"https://github.com/kubernetes/kubernetes/pull/15775.patch","issue_url":"https://api.github.com/repos/kubernetes/kubernetes/issues/15775","number":15775,"state":"open","locked":false,"title":"WIP/MESOS: scheduler: implement role awareness","user":{"login":"s-urbaniak","id":375856,"avatar_url":"https://avatars.githubusercontent.com/u/375856?v=3","gravatar_id":"","url":"https://api.github.com/users/s-urbaniak","html_url":"https://github.com/s-urbaniak","followers_url":"https://api.github.com/users/s-urbaniak/followers","following_url":"https://api.github.com/users/s-urbaniak/following{/other_user}","gists_url":"https://api.github.com/users/s-urbaniak/gists{/gist_id}","starred_url":"https://api.github.com/users/s-urbaniak/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/s-urbaniak/subscriptions","organizations_url":"https://api.github.com/users/s-urbaniak/orgs","repos_url":"https://api.github.com/users/s-urbaniak/repos","events_url":"https://api.github.com/users/s-urbaniak/events{/privacy}","received_events_url":"https://api.github.com/users/s-urbaniak/received_events","type":"User","site_admin":false},"body":"This commit implements role awareness in the Mesos scheduler.\r\n\r\nTODO:\r\n- [x] rebase on scheduler refactoring\r\n- [x] reimplement kamikaze debug endpoint to work with changing executor ids (we probably need a slave -> executorId dictionary, refilled on recover from the executorId field in the pod) https://github.com/mesosphere/kubernetes/pull/46\r\n- [x] do not pass the ExecutorInfo prototype into the framework https://github.com/mesosphere/kubernetes/pull/46\r\n- [x] don't pass the executor prototype into the procurer, but only the executor resources https://github.com/mesosphere/kubernetes/pull/47\r\n- [x] implement race-free node update in executor on Register/Reregister\r\n- [ ] **sur** implement ExecutorInfo registry TTL (one day)\r\n- [x] **sttts** write roles.md https://github.com/kubernetes/kubernetes/pull/17368\r\n- [x] **sttts** write labels.md https://github.com/kubernetes/kubernetes/pull/17368\r\n- [x] **sttts** document that changed executor command line leads to TASK_ERRORs https://github.com/mesosphere/kubernetes/pull/48\r\n- [x] **sur** document the pipeline nature of the Procure spec vs. the task spec https://github.com/kubernetes/kubernetes/pull/15775/files#r44934851\r\n- [x]  **sur** document that the task spec is read-only everywhere once set after successful procument\r\n- [x] **sur** add \"else case\" for https://github.com/kubernetes/kubernetes/pull/15775/files#r44935304\r\n- [x] **sur** make setCommandArgument nil safe\r\n- [x] **sur** rename newRangesResource to newPortsResource https://github.com/kubernetes/kubernetes/pull/15775/files#r44929561\r\n- [x] **sur** rename procureRoleResources to procureScalarResources https://github.com/kubernetes/kubernetes/pull/15775/files#r44931612\r\n- [x] **sur** rename newResource to scalar https://github.com/kubernetes/kubernetes/pull/15775/files#r44933368\r\n- [x] **sur** fix comment https://github.com/kubernetes/kubernetes/pull/15775/files#r44935396\r\n- [x] **sur** document https://github.com/kubernetes/kubernetes/pull/15775/files#r45032746\r\n- [x] **sur** fix comments:\r\n  - https://github.com/kubernetes/kubernetes/pull/15775/files#r45033276\r\n  - https://github.com/kubernetes/kubernetes/pull/15775/files#r45033326\r\n  - https://github.com/kubernetes/kubernetes/pull/15775/files#r45033397\r\n- [x] **sur** document `executorinfo.Registry`, write unit tests\r\n\r\nFor follow-up PRs:\r\n\r\n- [ ] add procurement of resources between requests and limit\r\n- [ ] solve \"changing ExecutorInfo\" problem https://github.com/mesosphere/kubernetes-mesos/issues/506\r\n- [ ] refactor debug handler in a separate go file\r\n- [ ] add debug endpoint listing all known executors known to the scheduler\r\n\r\nxref: https://github.com/mesosphere/kubernetes-mesos/issues/482","created_at":"2015-10-16T11:57:54Z","updated_at":"2015-11-18T15:05:07Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bedc6b3b3edce1308c0e9f50dfbb43e550715a9d","assignee":{"login":"jdef","id":2348332,"avatar_url":"https://avatars.githubusercontent.com/u/2348332?v=3","gravatar_id":"","url":"https://api.github.com/users/jdef","html_url":"https://github.com/jdef","followers_url":"https://api.github.com/users/jdef/followers","following_url":"https://api.github.com/users/jdef/following{/other_user}","gists_url":"https://api.github.com/users/jdef/gists{/gist_id}","starred_url":"https://api.github.com/users/jdef/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdef/subscriptions","organizations_url":"https://api.github.com/users/jdef/orgs","repos_url":"https://api.github.com/users/jdef/repos","events_url":"https://api.github.com/users/jdef/events{/privacy}","received_events_url":"https://api.github.com/users/jdef/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/15775/commits","review_comments_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/15775/comments","review_comment_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kubernetes/kubernetes/issues/15775/comments","statuses_url":"https://api.github.com/repos/kubernetes/kubernetes/statuses/538256d864262a0a21440fa5caa6121bf4215673","head":{"label":"mesosphere:sur-k8sm-482-resource-roles","ref":"sur-k8sm-482-resource-roles","sha":"538256d864262a0a21440fa5caa6121bf4215673","user":{"login":"mesosphere","id":3458585,"avatar_url":"https://avatars.githubusercontent.com/u/3458585?v=3","gravatar_id":"","url":"https://api.github.com/users/mesosphere","html_url":"https://github.com/mesosphere","followers_url":"https://api.github.com/users/mesosphere/followers","following_url":"https://api.github.com/users/mesosphere/following{/other_user}","gists_url":"https://api.github.com/users/mesosphere/gists{/gist_id}","starred_url":"https://api.github.com/users/mesosphere/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mesosphere/subscriptions","organizations_url":"https://api.github.com/users/mesosphere/orgs","repos_url":"https://api.github.com/users/mesosphere/repos","events_url":"https://api.github.com/users/mesosphere/events{/privacy}","received_events_url":"https://api.github.com/users/mesosphere/received_events","type":"Organization","site_admin":false},"repo":{"id":22277046,"name":"kubernetes","full_name":"mesosphere/kubernetes","owner":{"login":"mesosphere","id":3458585,"avatar_url":"https://avatars.githubusercontent.com/u/3458585?v=3","gravatar_id":"","url":"https://api.github.com/users/mesosphere","html_url":"https://github.com/mesosphere","followers_url":"https://api.github.com/users/mesosphere/followers","following_url":"https://api.github.com/users/mesosphere/following{/other_user}","gists_url":"https://api.github.com/users/mesosphere/gists{/gist_id}","starred_url":"https://api.github.com/users/mesosphere/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mesosphere/subscriptions","organizations_url":"https://api.github.com/users/mesosphere/orgs","repos_url":"https://api.github.com/users/mesosphere/repos","events_url":"https://api.github.com/users/mesosphere/events{/privacy}","received_events_url":"https://api.github.com/users/mesosphere/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mesosphere/kubernetes","description":"Container Cluster Manager","fork":true,"url":"https://api.github.com/repos/mesosphere/kubernetes","forks_url":"https://api.github.com/repos/mesosphere/kubernetes/forks","keys_url":"https://api.github.com/repos/mesosphere/kubernetes/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mesosphere/kubernetes/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mesosphere/kubernetes/teams","hooks_url":"https://api.github.com/repos/mesosphere/kubernetes/hooks","issue_events_url":"https://api.github.com/repos/mesosphere/kubernetes/issues/events{/number}","events_url":"https://api.github.com/repos/mesosphere/kubernetes/events","assignees_url":"https://api.github.com/repos/mesosphere/kubernetes/assignees{/user}","branches_url":"https://api.github.com/repos/mesosphere/kubernetes/branches{/branch}","tags_url":"https://api.github.com/repos/mesosphere/kubernetes/tags","blobs_url":"https://api.github.com/repos/mesosphere/kubernetes/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mesosphere/kubernetes/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mesosphere/kubernetes/git/refs{/sha}","trees_url":"https://api.github.com/repos/mesosphere/kubernetes/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mesosphere/kubernetes/statuses/{sha}","languages_url":"https://api.github.com/repos/mesosphere/kubernetes/languages","stargazers_url":"https://api.github.com/repos/mesosphere/kubernetes/stargazers","contributors_url":"https://api.github.com/repos/mesosphere/kubernetes/contributors","subscribers_url":"https://api.github.com/repos/mesosphere/kubernetes/subscribers","subscription_url":"https://api.github.com/repos/mesosphere/kubernetes/subscription","commits_url":"https://api.github.com/repos/mesosphere/kubernetes/commits{/sha}","git_commits_url":"https://api.github.com/repos/mesosphere/kubernetes/git/commits{/sha}","comments_url":"https://api.github.com/repos/mesosphere/kubernetes/comments{/number}","issue_comment_url":"https://api.github.com/repos/mesosphere/kubernetes/issues/comments{/number}","contents_url":"https://api.github.com/repos/mesosphere/kubernetes/contents/{+path}","compare_url":"https://api.github.com/repos/mesosphere/kubernetes/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mesosphere/kubernetes/merges","archive_url":"https://api.github.com/repos/mesosphere/kubernetes/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mesosphere/kubernetes/downloads","issues_url":"https://api.github.com/repos/mesosphere/kubernetes/issues{/number}","pulls_url":"https://api.github.com/repos/mesosphere/kubernetes/pulls{/number}","milestones_url":"https://api.github.com/repos/mesosphere/kubernetes/milestones{/number}","notifications_url":"https://api.github.com/repos/mesosphere/kubernetes/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mesosphere/kubernetes/labels{/name}","releases_url":"https://api.github.com/repos/mesosphere/kubernetes/releases{/id}","created_at":"2014-07-26T02:50:13Z","updated_at":"2015-10-18T15:27:35Z","pushed_at":"2015-11-18T15:03:39Z","git_url":"git://github.com/mesosphere/kubernetes.git","ssh_url":"git@github.com:mesosphere/kubernetes.git","clone_url":"https://github.com/mesosphere/kubernetes.git","svn_url":"https://github.com/mesosphere/kubernetes","homepage":null,"size":138140,"stargazers_count":11,"watchers_count":11,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":4,"mirror_url":null,"open_issues_count":3,"forks":4,"open_issues":3,"watchers":11,"default_branch":"master"}},"base":{"label":"kubernetes:master","ref":"master","sha":"14eae4b48ec58e64529e36852ba24e9d0b33bd6b","user":{"login":"kubernetes","id":13629408,"avatar_url":"https://avatars.githubusercontent.com/u/13629408?v=3","gravatar_id":"","url":"https://api.github.com/users/kubernetes","html_url":"https://github.com/kubernetes","followers_url":"https://api.github.com/users/kubernetes/followers","following_url":"https://api.github.com/users/kubernetes/following{/other_user}","gists_url":"https://api.github.com/users/kubernetes/gists{/gist_id}","starred_url":"https://api.github.com/users/kubernetes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kubernetes/subscriptions","organizations_url":"https://api.github.com/users/kubernetes/orgs","repos_url":"https://api.github.com/users/kubernetes/repos","events_url":"https://api.github.com/users/kubernetes/events{/privacy}","received_events_url":"https://api.github.com/users/kubernetes/received_events","type":"Organization","site_admin":false},"repo":{"id":20580498,"name":"kubernetes","full_name":"kubernetes/kubernetes","owner":{"login":"kubernetes","id":13629408,"avatar_url":"https://avatars.githubusercontent.com/u/13629408?v=3","gravatar_id":"","url":"https://api.github.com/users/kubernetes","html_url":"https://github.com/kubernetes","followers_url":"https://api.github.com/users/kubernetes/followers","following_url":"https://api.github.com/users/kubernetes/following{/other_user}","gists_url":"https://api.github.com/users/kubernetes/gists{/gist_id}","starred_url":"https://api.github.com/users/kubernetes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kubernetes/subscriptions","organizations_url":"https://api.github.com/users/kubernetes/orgs","repos_url":"https://api.github.com/users/kubernetes/repos","events_url":"https://api.github.com/users/kubernetes/events{/privacy}","received_events_url":"https://api.github.com/users/kubernetes/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/kubernetes/kubernetes","description":"Container Cluster Manager from Google","fork":false,"url":"https://api.github.com/repos/kubernetes/kubernetes","forks_url":"https://api.github.com/repos/kubernetes/kubernetes/forks","keys_url":"https://api.github.com/repos/kubernetes/kubernetes/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kubernetes/kubernetes/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kubernetes/kubernetes/teams","hooks_url":"https://api.github.com/repos/kubernetes/kubernetes/hooks","issue_events_url":"https://api.github.com/repos/kubernetes/kubernetes/issues/events{/number}","events_url":"https://api.github.com/repos/kubernetes/kubernetes/events","assignees_url":"https://api.github.com/repos/kubernetes/kubernetes/assignees{/user}","branches_url":"https://api.github.com/repos/kubernetes/kubernetes/branches{/branch}","tags_url":"https://api.github.com/repos/kubernetes/kubernetes/tags","blobs_url":"https://api.github.com/repos/kubernetes/kubernetes/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kubernetes/kubernetes/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kubernetes/kubernetes/git/refs{/sha}","trees_url":"https://api.github.com/repos/kubernetes/kubernetes/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kubernetes/kubernetes/statuses/{sha}","languages_url":"https://api.github.com/repos/kubernetes/kubernetes/languages","stargazers_url":"https://api.github.com/repos/kubernetes/kubernetes/stargazers","contributors_url":"https://api.github.com/repos/kubernetes/kubernetes/contributors","subscribers_url":"https://api.github.com/repos/kubernetes/kubernetes/subscribers","subscription_url":"https://api.github.com/repos/kubernetes/kubernetes/subscription","commits_url":"https://api.github.com/repos/kubernetes/kubernetes/commits{/sha}","git_commits_url":"https://api.github.com/repos/kubernetes/kubernetes/git/commits{/sha}","comments_url":"https://api.github.com/repos/kubernetes/kubernetes/comments{/number}","issue_comment_url":"https://api.github.com/repos/kubernetes/kubernetes/issues/comments{/number}","contents_url":"https://api.github.com/repos/kubernetes/kubernetes/contents/{+path}","compare_url":"https://api.github.com/repos/kubernetes/kubernetes/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kubernetes/kubernetes/merges","archive_url":"https://api.github.com/repos/kubernetes/kubernetes/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kubernetes/kubernetes/downloads","issues_url":"https://api.github.com/repos/kubernetes/kubernetes/issues{/number}","pulls_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls{/number}","milestones_url":"https://api.github.com/repos/kubernetes/kubernetes/milestones{/number}","notifications_url":"https://api.github.com/repos/kubernetes/kubernetes/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kubernetes/kubernetes/labels{/name}","releases_url":"https://api.github.com/repos/kubernetes/kubernetes/releases{/id}","created_at":"2014-06-06T22:56:04Z","updated_at":"2015-11-18T13:38:44Z","pushed_at":"2015-11-18T15:03:41Z","git_url":"git://github.com/kubernetes/kubernetes.git","ssh_url":"git@github.com:kubernetes/kubernetes.git","clone_url":"https://github.com/kubernetes/kubernetes.git","svn_url":"https://github.com/kubernetes/kubernetes","homepage":"http://kubernetes.io","size":287649,"stargazers_count":11320,"watchers_count":11320,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":2987,"mirror_url":null,"open_issues_count":2345,"forks":2987,"open_issues":2345,"watchers":11320,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/15775"},"html":{"href":"https://github.com/kubernetes/kubernetes/pull/15775"},"issue":{"href":"https://api.github.com/repos/kubernetes/kubernetes/issues/15775"},"comments":{"href":"https://api.github.com/repos/kubernetes/kubernetes/issues/15775/comments"},"review_comments":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/15775/comments"},"review_comment":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/15775/commits"},"statuses":{"href":"https://api.github.com/repos/kubernetes/kubernetes/statuses/538256d864262a0a21440fa5caa6121bf4215673"}}}}