func newLifecycleTest(t *testing.T) lifecycleTest { assert := &EventAssertions{*assert.New(t)} // create a fake pod watch. We use that below to submit new pods to the scheduler podsListWatch := NewMockPodsListWatch(api.PodList{}) // create fake apiserver apiServer := NewTestServer(t, api.NamespaceDefault, podsListWatch) // create ExecutorInfo with some data for static pods if set ei := mesosutil.NewExecutorInfo( mesosutil.NewExecutorID("executor-id"), mesosutil.NewCommandInfo("executor-cmd"), ) ei.Data = []byte{0, 1, 2} // create framework client := client.NewOrDie(&client.Config{ Host: apiServer.server.URL, GroupVersion: testapi.Default.GroupVersion(), }) c := *schedcfg.CreateDefaultConfig() fw := framework.New(framework.Config{ Executor: ei, Client: client, SchedulerConfig: c, LookupNode: apiServer.LookupNode, }) // TODO(sttts): re-enable the following tests // assert.NotNil(framework.client, "client is nil") // assert.NotNil(framework.executor, "executor is nil") // assert.NotNil(framework.offers, "offer registry is nil") // create pod scheduler strategy := podschedulers.NewAllocationStrategy( podtask.NewDefaultPredicate( mresource.DefaultDefaultContainerCPULimit, mresource.DefaultDefaultContainerMemLimit, ), podtask.NewDefaultProcurement( mresource.DefaultDefaultContainerCPULimit, mresource.DefaultDefaultContainerMemLimit, ), ) fcfs := podschedulers.NewFCFSPodScheduler(strategy, apiServer.LookupNode) // create scheduler process schedulerProc := ha.New(fw) // create scheduler eventObs := NewEventObserver() scheduler := components.New(&c, fw, fcfs, client, eventObs, schedulerProc.Terminal(), http.DefaultServeMux, &podsListWatch.ListWatch) assert.NotNil(scheduler) // create mock mesos scheduler driver driver := &framework.JoinableDriver{} return lifecycleTest{ apiServer: apiServer, driver: driver, eventObs: eventObs, podsListWatch: podsListWatch, framework: fw, schedulerProc: schedulerProc, sched: scheduler, t: t, } }
// Test to create the scheduler plugin with the config returned by the scheduler, // and play through the whole life cycle of the plugin while creating pods, deleting // and failing them. func TestPlugin_LifeCycle(t *testing.T) { t.Skip("This test is flaky, see #11901") assert := &EventAssertions{*assert.New(t)} // create a fake pod watch. We use that below to submit new pods to the scheduler podListWatch := NewMockPodsListWatch(api.PodList{}) // create fake apiserver testApiServer := NewTestServer(t, api.NamespaceDefault, podListWatch) defer testApiServer.server.Close() // create executor with some data for static pods if set executor := util.NewExecutorInfo( util.NewExecutorID("executor-id"), util.NewCommandInfo("executor-cmd"), ) executor.Data = []byte{0, 1, 2} // create scheduler nodeStore := cache.NewStore(cache.MetaNamespaceKeyFunc) as := NewAllocationStrategy( podtask.DefaultPredicate, podtask.NewDefaultProcurement(mresource.DefaultDefaultContainerCPULimit, mresource.DefaultDefaultContainerMemLimit)) testScheduler := New(Config{ Executor: executor, Client: client.NewOrDie(&client.Config{Host: testApiServer.server.URL, Version: testapi.Default.Version()}), Scheduler: NewFCFSPodScheduler(as, func(node string) *api.Node { obj, _, _ := nodeStore.GetByKey(node) if obj == nil { return nil } return obj.(*api.Node) }), Schedcfg: *schedcfg.CreateDefaultConfig(), }) assert.NotNil(testScheduler.client, "client is nil") assert.NotNil(testScheduler.executor, "executor is nil") assert.NotNil(testScheduler.offers, "offer registry is nil") // create scheduler process schedulerProcess := ha.New(testScheduler) // get plugin config from it c := testScheduler.NewPluginConfig(schedulerProcess.Terminal(), http.DefaultServeMux, &podListWatch.ListWatch) assert.NotNil(c) // make events observable eventObserver := NewEventObserver() c.Recorder = eventObserver // create plugin p := NewPlugin(c).(*schedulingPlugin) assert.NotNil(p) // run plugin p.Run(schedulerProcess.Terminal()) defer schedulerProcess.End() // init scheduler err := testScheduler.Init(schedulerProcess.Master(), p, http.DefaultServeMux) assert.NoError(err) // create mock mesos scheduler driver mockDriver := &joinableDriver{} mockDriver.On("Start").Return(mesos.Status_DRIVER_RUNNING, nil).Once() started := mockDriver.Upon() mAny := mock.AnythingOfType mockDriver.On("ReconcileTasks", mAny("[]*mesosproto.TaskStatus")).Return(mesos.Status_DRIVER_RUNNING, nil) mockDriver.On("SendFrameworkMessage", mAny("*mesosproto.ExecutorID"), mAny("*mesosproto.SlaveID"), mAny("string")). Return(mesos.Status_DRIVER_RUNNING, nil) type LaunchedTask struct { offerId mesos.OfferID taskInfo *mesos.TaskInfo } launchedTasks := make(chan LaunchedTask, 1) launchTasksCalledFunc := func(args mock.Arguments) { offerIDs := args.Get(0).([]*mesos.OfferID) taskInfos := args.Get(1).([]*mesos.TaskInfo) assert.Equal(1, len(offerIDs)) assert.Equal(1, len(taskInfos)) launchedTasks <- LaunchedTask{ offerId: *offerIDs[0], taskInfo: taskInfos[0], } } mockDriver.On("LaunchTasks", mAny("[]*mesosproto.OfferID"), mAny("[]*mesosproto.TaskInfo"), mAny("*mesosproto.Filters")). Return(mesos.Status_DRIVER_RUNNING, nil).Run(launchTasksCalledFunc) mockDriver.On("DeclineOffer", mAny("*mesosproto.OfferID"), mAny("*mesosproto.Filters")). Return(mesos.Status_DRIVER_RUNNING, nil) // elect master with mock driver driverFactory := ha.DriverFactory(func() (bindings.SchedulerDriver, error) { return mockDriver, nil }) schedulerProcess.Elect(driverFactory) elected := schedulerProcess.Elected() // driver will be started <-started // tell scheduler to be registered testScheduler.Registered( mockDriver, util.NewFrameworkID("kubernetes-id"), util.NewMasterInfo("master-id", (192<<24)+(168<<16)+(0<<8)+1, 5050), ) // wait for being elected <-elected //TODO(jdef) refactor things above here into a test suite setup of some sort // fake new, unscheduled pod pod, i := NewTestPod() podListWatch.Add(pod, true) // notify watchers // wait for failedScheduling event because there is no offer assert.EventWithReason(eventObserver, "failedScheduling", "failedScheduling event not received") // add some matching offer offers := []*mesos.Offer{NewTestOffer(fmt.Sprintf("offer%d", i))} testScheduler.ResourceOffers(nil, offers) // and wait for scheduled pod assert.EventWithReason(eventObserver, "scheduled") select { case launchedTask := <-launchedTasks: // report back that the task has been staged, and then started by mesos testScheduler.StatusUpdate(mockDriver, newTaskStatusForTask(launchedTask.taskInfo, mesos.TaskState_TASK_STAGING)) testScheduler.StatusUpdate(mockDriver, newTaskStatusForTask(launchedTask.taskInfo, mesos.TaskState_TASK_RUNNING)) // check that ExecutorInfo.data has the static pod data assert.Len(launchedTask.taskInfo.Executor.Data, 3) // report back that the task has been lost mockDriver.AssertNumberOfCalls(t, "SendFrameworkMessage", 0) testScheduler.StatusUpdate(mockDriver, newTaskStatusForTask(launchedTask.taskInfo, mesos.TaskState_TASK_LOST)) // and wait that framework message is sent to executor mockDriver.AssertNumberOfCalls(t, "SendFrameworkMessage", 1) case <-time.After(5 * time.Second): t.Fatalf("timed out waiting for launchTasks call") } // Launch a pod and wait until the scheduler driver is called schedulePodWithOffers := func(pod *api.Pod, offers []*mesos.Offer) (*api.Pod, *LaunchedTask, *mesos.Offer) { // wait for failedScheduling event because there is no offer assert.EventWithReason(eventObserver, "failedScheduling", "failedScheduling event not received") // supply a matching offer testScheduler.ResourceOffers(mockDriver, offers) // and wait to get scheduled assert.EventWithReason(eventObserver, "scheduled") // wait for driver.launchTasks call select { case launchedTask := <-launchedTasks: for _, offer := range offers { if offer.Id.GetValue() == launchedTask.offerId.GetValue() { return pod, &launchedTask, offer } } t.Fatalf("unknown offer used to start a pod") return nil, nil, nil case <-time.After(5 * time.Second): t.Fatal("timed out waiting for launchTasks") return nil, nil, nil } } // Launch a pod and wait until the scheduler driver is called launchPodWithOffers := func(pod *api.Pod, offers []*mesos.Offer) (*api.Pod, *LaunchedTask, *mesos.Offer) { podListWatch.Add(pod, true) return schedulePodWithOffers(pod, offers) } // Launch a pod, wait until the scheduler driver is called and report back that it is running startPodWithOffers := func(pod *api.Pod, offers []*mesos.Offer) (*api.Pod, *LaunchedTask, *mesos.Offer) { // notify about pod, offer resources and wait for scheduling pod, launchedTask, offer := launchPodWithOffers(pod, offers) if pod != nil { // report back status testScheduler.StatusUpdate(mockDriver, newTaskStatusForTask(launchedTask.taskInfo, mesos.TaskState_TASK_STAGING)) testScheduler.StatusUpdate(mockDriver, newTaskStatusForTask(launchedTask.taskInfo, mesos.TaskState_TASK_RUNNING)) return pod, launchedTask, offer } return nil, nil, nil } startTestPod := func() (*api.Pod, *LaunchedTask, *mesos.Offer) { pod, i := NewTestPod() offers := []*mesos.Offer{NewTestOffer(fmt.Sprintf("offer%d", i))} return startPodWithOffers(pod, offers) } // start another pod pod, launchedTask, _ := startTestPod() // mock drvier.KillTask, should be invoked when a pod is deleted mockDriver.On("KillTask", mAny("*mesosproto.TaskID")).Return(mesos.Status_DRIVER_RUNNING, nil).Run(func(args mock.Arguments) { killedTaskId := *(args.Get(0).(*mesos.TaskID)) assert.Equal(*launchedTask.taskInfo.TaskId, killedTaskId, "expected same TaskID as during launch") }) killTaskCalled := mockDriver.Upon() // stop it again via the apiserver mock podListWatch.Delete(pod, true) // notify watchers // and wait for the driver killTask call with the correct TaskId select { case <-killTaskCalled: // report back that the task is finished testScheduler.StatusUpdate(mockDriver, newTaskStatusForTask(launchedTask.taskInfo, mesos.TaskState_TASK_FINISHED)) case <-time.After(5 * time.Second): t.Fatal("timed out waiting for KillTask") } // start a pod with on a given NodeName and check that it is scheduled to the right host pod, i = NewTestPod() pod.Spec.NodeName = "hostname1" offers = []*mesos.Offer{} for j := 0; j < 3; j++ { offer := NewTestOffer(fmt.Sprintf("offer%d_%d", i, j)) hostname := fmt.Sprintf("hostname%d", j) offer.Hostname = &hostname offers = append(offers, offer) } _, _, usedOffer := startPodWithOffers(pod, offers) assert.Equal(offers[1].Id.GetValue(), usedOffer.Id.GetValue()) assert.Equal(pod.Spec.NodeName, *usedOffer.Hostname) testScheduler.OfferRescinded(mockDriver, offers[0].Id) testScheduler.OfferRescinded(mockDriver, offers[2].Id) // start pods: // - which are failing while binding, // - leading to reconciliation // - with different states on the apiserver failPodFromExecutor := func(task *mesos.TaskInfo) { beforePodLookups := testApiServer.Stats(pod.Name) status := newTaskStatusForTask(task, mesos.TaskState_TASK_FAILED) message := messages.CreateBindingFailure status.Message = &message testScheduler.StatusUpdate(mockDriver, status) // wait until pod is looked up at the apiserver assertext.EventuallyTrue(t, time.Second, func() bool { return testApiServer.Stats(pod.Name) == beforePodLookups+1 }, "expect that reconcileTask will access apiserver for pod %v", pod.Name) } launchTestPod := func() (*api.Pod, *LaunchedTask, *mesos.Offer) { pod, i := NewTestPod() offers := []*mesos.Offer{NewTestOffer(fmt.Sprintf("offer%d", i))} return launchPodWithOffers(pod, offers) } // 1. with pod deleted from the apiserver // expected: pod is removed from internal task registry pod, launchedTask, _ = launchTestPod() podListWatch.Delete(pod, false) // not notifying the watchers failPodFromExecutor(launchedTask.taskInfo) podKey, _ := podtask.MakePodKey(api.NewDefaultContext(), pod.Name) assertext.EventuallyTrue(t, time.Second, func() bool { t, _ := p.api.tasks().ForPod(podKey) return t == nil }) // 2. with pod still on the apiserver, not bound // expected: pod is rescheduled pod, launchedTask, _ = launchTestPod() failPodFromExecutor(launchedTask.taskInfo) retryOffers := []*mesos.Offer{NewTestOffer("retry-offer")} schedulePodWithOffers(pod, retryOffers) // 3. with pod still on the apiserver, bound, notified via ListWatch // expected: nothing, pod updates not supported, compare ReconcileTask function pod, launchedTask, usedOffer = startTestPod() pod.Annotations = map[string]string{ meta.BindingHostKey: *usedOffer.Hostname, } pod.Spec.NodeName = *usedOffer.Hostname podListWatch.Modify(pod, true) // notifying the watchers time.Sleep(time.Second / 2) failPodFromExecutor(launchedTask.taskInfo) }
func (s *SchedulerServer) bootstrap(hks hyperkube.Interface, sc *schedcfg.Config) (*ha.SchedulerProcess, ha.DriverFactory, etcd.Client, *mesos.ExecutorID) { s.frameworkName = strings.TrimSpace(s.frameworkName) if s.frameworkName == "" { log.Fatalf("framework-name must be a non-empty string") } s.frameworkWebURI = strings.TrimSpace(s.frameworkWebURI) metrics.Register() runtime.Register() s.mux.Handle("/metrics", prometheus.Handler()) healthz.InstallHandler(s.mux) if len(s.etcdServerList) == 0 { log.Fatalf("specify --etcd-servers must be specified") } if len(s.apiServerList) < 1 { log.Fatal("No api servers specified.") } clientConfig, err := s.createAPIServerClientConfig() if err != nil { log.Fatalf("Unable to make apiserver client config: %v", err) } s.client, err = clientset.NewForConfig(clientConfig) if err != nil { log.Fatalf("Unable to make apiserver clientset: %v", err) } if s.reconcileCooldown < defaultReconcileCooldown { s.reconcileCooldown = defaultReconcileCooldown log.Warningf("user-specified reconcile cooldown too small, defaulting to %v", s.reconcileCooldown) } eiPrototype, err := s.prepareExecutorInfo(hks) if err != nil { log.Fatalf("misconfigured executor: %v", err) } // TODO(jdef): remove the dependency on etcd as soon as // (1) the generic config store is available for the FrameworkId storage // (2) the generic master election is provided by the apiserver // Compare docs/proposals/high-availability.md etcdClient, err := newEtcd(s.etcdServerList) if err != nil { log.Fatalf("misconfigured etcd: %v", err) } keysAPI := etcd.NewKeysAPI(etcdClient) // mirror all nodes into the nodeStore var eiRegistry executorinfo.Registry nodesClientConfig := *clientConfig nodesClient, err := clientset.NewForConfig(&nodesClientConfig) if err != nil { log.Fatalf("Cannot create client to watch nodes: %v", err) } nodeLW := cache.NewListWatchFromClient(nodesClient.CoreClient, "nodes", api.NamespaceAll, fields.Everything()) nodeStore, nodeCtl := controllerfw.NewInformer(nodeLW, &api.Node{}, s.nodeRelistPeriod, &controllerfw.ResourceEventHandlerFuncs{ DeleteFunc: func(obj interface{}) { if eiRegistry != nil { // TODO(jdef) use controllerfw.DeletionHandlingMetaNamespaceKeyFunc at some point? nodeName := "" if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { nodeName = tombstone.Key } else if node, ok := obj.(*api.Node); ok { nodeName = node.Name } if nodeName != "" { log.V(2).Infof("deleting node %q from registry", nodeName) eiRegistry.Invalidate(nodeName) } } }, }) lookupNode := func(hostName string) *api.Node { n, _, _ := nodeStore.GetByKey(hostName) // ignore error and return nil then if n == nil { return nil } return n.(*api.Node) } execInfoCache, err := executorinfo.NewCache(defaultExecutorInfoCacheSize) if err != nil { log.Fatalf("cannot create executorinfo cache: %v", err) } eiRegistry, err = executorinfo.NewRegistry(lookupNode, eiPrototype, execInfoCache) if err != nil { log.Fatalf("cannot create executorinfo registry: %v", err) } pr := podtask.NewDefaultProcurement(eiPrototype, eiRegistry) fcfs := podschedulers.NewFCFSPodScheduler(pr, lookupNode) frameworkIDStorage, err := s.frameworkIDStorage(keysAPI) if err != nil { log.Fatalf("cannot init framework ID storage: %v", err) } framework := framework.New(framework.Config{ SchedulerConfig: *sc, Client: s.client, FailoverTimeout: s.failoverTimeout, ReconcileInterval: s.reconcileInterval, ReconcileCooldown: s.reconcileCooldown, LookupNode: lookupNode, StoreFrameworkId: frameworkIDStorage.Set, ExecutorId: eiPrototype.GetExecutorId(), }) masterUri := s.mesosMaster info, cred, err := s.buildFrameworkInfo() if err != nil { log.Fatalf("Misconfigured mesos framework: %v", err) } schedulerProcess := ha.New(framework) // try publishing on the same IP as the slave var publishedAddress net.IP if libprocessIP := os.Getenv("LIBPROCESS_IP"); libprocessIP != "" { publishedAddress = net.ParseIP(libprocessIP) } if publishedAddress != nil { log.V(1).Infof("driver will publish address %v", publishedAddress) } dconfig := &bindings.DriverConfig{ Scheduler: schedulerProcess, Framework: info, Master: masterUri, Credential: cred, BindingAddress: s.address, BindingPort: uint16(s.driverPort), PublishedAddress: publishedAddress, HostnameOverride: s.hostnameOverride, WithAuthContext: func(ctx context.Context) context.Context { ctx = auth.WithLoginProvider(ctx, s.mesosAuthProvider) ctx = sasl.WithBindingAddress(ctx, s.address) return ctx }, } // create event recorder sending events to the "" namespace of the apiserver eventsClientConfig := *clientConfig eventsClient, err := clientset.NewForConfig(&eventsClientConfig) if err != nil { log.Fatalf("Invalid API configuration: %v", err) } broadcaster := record.NewBroadcaster() recorder := broadcaster.NewRecorder(api.EventSource{Component: api.DefaultSchedulerName}) broadcaster.StartLogging(log.Infof) broadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: eventsClient.Events("")}) lw := cache.NewListWatchFromClient(s.client.CoreClient, "pods", api.NamespaceAll, fields.Everything()) hostPortStrategy := hostport.StrategyFixed if s.useHostPortEndpoints { hostPortStrategy = hostport.StrategyWildcard } // create scheduler core with all components arranged around it sched := components.New( sc, framework, fcfs, s.client, recorder, schedulerProcess.Terminal(), s.mux, lw, podtask.Config{ DefaultPodRoles: s.defaultPodRoles, FrameworkRoles: s.frameworkRoles, GenerateTaskDiscoveryEnabled: s.generateTaskDiscovery, HostPortStrategy: hostPortStrategy, Prototype: eiPrototype, }, s.defaultContainerCPULimit, s.defaultContainerMemLimit, ) runtime.On(framework.Registration(), func() { sched.Run(schedulerProcess.Terminal()) }) runtime.On(framework.Registration(), s.newServiceWriter(publishedAddress, schedulerProcess.Terminal())) runtime.On(framework.Registration(), func() { nodeCtl.Run(schedulerProcess.Terminal()) }) driverFactory := ha.DriverFactory(func() (drv bindings.SchedulerDriver, err error) { log.V(1).Infoln("performing deferred initialization") if err = framework.Init(sched, schedulerProcess.Master(), s.mux); err != nil { return nil, fmt.Errorf("failed to initialize pod scheduler: %v", err) } log.V(1).Infoln("deferred init complete") if s.failoverTimeout > 0 { // defer obtaining framework ID to prevent multiple schedulers // from overwriting each other's framework IDs var frameworkID string frameworkID, err = frameworkIDStorage.Get(context.TODO()) if err != nil { return nil, fmt.Errorf("failed to fetch framework ID from storage: %v", err) } if frameworkID != "" { log.Infof("configuring FrameworkInfo with ID found in storage: %q", frameworkID) dconfig.Framework.Id = &mesos.FrameworkID{Value: &frameworkID} } else { log.V(1).Infof("did not find framework ID in storage") } } else { // TODO(jdef) this is a hack, really for development, to simplify clean up of old framework IDs frameworkIDStorage.Remove(context.TODO()) } log.V(1).Infoln("constructing mesos scheduler driver") drv, err = bindings.NewMesosSchedulerDriver(*dconfig) if err != nil { return nil, fmt.Errorf("failed to construct scheduler driver: %v", err) } log.V(1).Infoln("constructed mesos scheduler driver:", drv) s.setDriver(drv) return drv, nil }) return schedulerProcess, driverFactory, etcdClient, eiPrototype.GetExecutorId() }
func (s *SchedulerServer) bootstrap(hks hyperkube.Interface, sc *schedcfg.Config) (*ha.SchedulerProcess, ha.DriverFactory, tools.EtcdClient, *uid.UID) { s.FrameworkName = strings.TrimSpace(s.FrameworkName) if s.FrameworkName == "" { log.Fatalf("framework-name must be a non-empty string") } s.FrameworkWebURI = strings.TrimSpace(s.FrameworkWebURI) metrics.Register() runtime.Register() s.mux.Handle("/metrics", prometheus.Handler()) healthz.InstallHandler(s.mux) if (s.EtcdConfigFile != "" && len(s.EtcdServerList) != 0) || (s.EtcdConfigFile == "" && len(s.EtcdServerList) == 0) { log.Fatalf("specify either --etcd-servers or --etcd-config") } if len(s.APIServerList) < 1 { log.Fatal("No api servers specified.") } client, err := s.createAPIServerClient() if err != nil { log.Fatalf("Unable to make apiserver client: %v", err) } s.client = client if s.ReconcileCooldown < defaultReconcileCooldown { s.ReconcileCooldown = defaultReconcileCooldown log.Warningf("user-specified reconcile cooldown too small, defaulting to %v", s.ReconcileCooldown) } executor, eid, err := s.prepareExecutorInfo(hks) if err != nil { log.Fatalf("misconfigured executor: %v", err) } // TODO(jdef): remove the dependency on etcd as soon as // (1) the generic config store is available for the FrameworkId storage // (2) the generic master election is provided by the apiserver // Compare docs/proposals/high-availability.md etcdClient, err := newEtcd(s.EtcdConfigFile, s.EtcdServerList) if err != nil { log.Fatalf("misconfigured etcd: %v", err) } as := scheduler.NewAllocationStrategy( podtask.NewDefaultPredicate( s.DefaultContainerCPULimit, s.DefaultContainerMemLimit, ), podtask.NewDefaultProcurement( s.DefaultContainerCPULimit, s.DefaultContainerMemLimit, ), ) // downgrade allocation strategy if user disables "account-for-pod-resources" if !s.AccountForPodResources { as = scheduler.NewAllocationStrategy( podtask.DefaultMinimalPredicate, podtask.DefaultMinimalProcurement) } // mirror all nodes into the nodeStore nodesClient, err := s.createAPIServerClient() if err != nil { log.Fatalf("Cannot create client to watch nodes: %v", err) } nodeStore := cache.NewStore(cache.MetaNamespaceKeyFunc) nodeLW := cache.NewListWatchFromClient(nodesClient, "nodes", api.NamespaceAll, fields.Everything()) cache.NewReflector(nodeLW, &api.Node{}, nodeStore, s.nodeRelistPeriod).Run() lookupNode := func(hostName string) *api.Node { n, _, _ := nodeStore.GetByKey(hostName) // ignore error and return nil then if n == nil { return nil } return n.(*api.Node) } fcfs := scheduler.NewFCFSPodScheduler(as, lookupNode) mesosPodScheduler := scheduler.New(scheduler.Config{ Schedcfg: *sc, Executor: executor, Scheduler: fcfs, Client: client, EtcdClient: etcdClient, FailoverTimeout: s.FailoverTimeout, ReconcileInterval: s.ReconcileInterval, ReconcileCooldown: s.ReconcileCooldown, LookupNode: lookupNode, }) masterUri := s.MesosMaster info, cred, err := s.buildFrameworkInfo() if err != nil { log.Fatalf("Misconfigured mesos framework: %v", err) } schedulerProcess := ha.New(mesosPodScheduler) dconfig := &bindings.DriverConfig{ Scheduler: schedulerProcess, Framework: info, Master: masterUri, Credential: cred, BindingAddress: s.Address, BindingPort: uint16(s.DriverPort), HostnameOverride: s.HostnameOverride, WithAuthContext: func(ctx context.Context) context.Context { ctx = auth.WithLoginProvider(ctx, s.MesosAuthProvider) ctx = sasl.WithBindingAddress(ctx, s.Address) return ctx }, } kpl := scheduler.NewPlugin(mesosPodScheduler.NewDefaultPluginConfig(schedulerProcess.Terminal(), s.mux)) runtime.On(mesosPodScheduler.Registration(), func() { kpl.Run(schedulerProcess.Terminal()) }) runtime.On(mesosPodScheduler.Registration(), s.newServiceWriter(schedulerProcess.Terminal())) driverFactory := ha.DriverFactory(func() (drv bindings.SchedulerDriver, err error) { log.V(1).Infoln("performing deferred initialization") if err = mesosPodScheduler.Init(schedulerProcess.Master(), kpl, s.mux); err != nil { return nil, fmt.Errorf("failed to initialize pod scheduler: %v", err) } log.V(1).Infoln("deferred init complete") // defer obtaining framework ID to prevent multiple schedulers // from overwriting each other's framework IDs dconfig.Framework.Id, err = s.fetchFrameworkID(etcdClient) if err != nil { return nil, fmt.Errorf("failed to fetch framework ID from etcd: %v", err) } log.V(1).Infoln("constructing mesos scheduler driver") drv, err = bindings.NewMesosSchedulerDriver(*dconfig) if err != nil { return nil, fmt.Errorf("failed to construct scheduler driver: %v", err) } log.V(1).Infoln("constructed mesos scheduler driver:", drv) s.setDriver(drv) return drv, nil }) return schedulerProcess, driverFactory, etcdClient, eid }
func (s *SchedulerServer) bootstrap(hks hyperkube.Interface, sc *schedcfg.Config) (*ha.SchedulerProcess, ha.DriverFactory, tools.EtcdClient, *uid.UID) { s.FrameworkName = strings.TrimSpace(s.FrameworkName) if s.FrameworkName == "" { log.Fatalf("framework-name must be a non-empty string") } s.FrameworkWebURI = strings.TrimSpace(s.FrameworkWebURI) metrics.Register() runtime.Register() s.mux.Handle("/metrics", prometheus.Handler()) if (s.EtcdConfigFile != "" && len(s.EtcdServerList) != 0) || (s.EtcdConfigFile == "" && len(s.EtcdServerList) == 0) { log.Fatalf("specify either --etcd-servers or --etcd-config") } if len(s.APIServerList) < 1 { log.Fatal("No api servers specified.") } client, err := s.createAPIServerClient() if err != nil { log.Fatalf("Unable to make apiserver client: %v", err) } s.client = client if s.ReconcileCooldown < defaultReconcileCooldown { s.ReconcileCooldown = defaultReconcileCooldown log.Warningf("user-specified reconcile cooldown too small, defaulting to %v", s.ReconcileCooldown) } executor, eid, err := s.prepareExecutorInfo(hks) if err != nil { log.Fatalf("misconfigured executor: %v", err) } // TODO(jdef): remove the dependency on etcd as soon as // (1) the generic config store is available for the FrameworkId storage // (2) the generic master election is provided by the apiserver // Compare docs/proposals/high-availability.md etcdClient, err := newEtcd(s.EtcdConfigFile, s.EtcdServerList) if err != nil { log.Fatalf("misconfigured etcd: %v", err) } mesosPodScheduler := scheduler.New(scheduler.Config{ Schedcfg: *sc, Executor: executor, ScheduleFunc: scheduler.FCFSScheduleFunc, Client: client, EtcdClient: etcdClient, FailoverTimeout: s.FailoverTimeout, ReconcileInterval: s.ReconcileInterval, ReconcileCooldown: s.ReconcileCooldown, DefaultContainerCPULimit: s.DefaultContainerCPULimit, DefaultContainerMemLimit: s.DefaultContainerMemLimit, }) masterUri := s.MesosMaster info, cred, err := s.buildFrameworkInfo() if err != nil { log.Fatalf("Misconfigured mesos framework: %v", err) } schedulerProcess := ha.New(mesosPodScheduler) dconfig := &bindings.DriverConfig{ Scheduler: schedulerProcess, Framework: info, Master: masterUri, Credential: cred, BindingAddress: net.IP(s.Address), BindingPort: uint16(s.DriverPort), HostnameOverride: s.HostnameOverride, WithAuthContext: func(ctx context.Context) context.Context { ctx = auth.WithLoginProvider(ctx, s.MesosAuthProvider) ctx = sasl.WithBindingAddress(ctx, net.IP(s.Address)) return ctx }, } kpl := scheduler.NewPlugin(mesosPodScheduler.NewDefaultPluginConfig(schedulerProcess.Terminal(), s.mux)) runtime.On(mesosPodScheduler.Registration(), func() { kpl.Run(schedulerProcess.Terminal()) }) runtime.On(mesosPodScheduler.Registration(), s.newServiceWriter(schedulerProcess.Terminal())) driverFactory := ha.DriverFactory(func() (drv bindings.SchedulerDriver, err error) { log.V(1).Infoln("performing deferred initialization") if err = mesosPodScheduler.Init(schedulerProcess.Master(), kpl, s.mux); err != nil { return nil, fmt.Errorf("failed to initialize pod scheduler: %v", err) } log.V(1).Infoln("deferred init complete") // defer obtaining framework ID to prevent multiple schedulers // from overwriting each other's framework IDs dconfig.Framework.Id, err = s.fetchFrameworkID(etcdClient) if err != nil { return nil, fmt.Errorf("failed to fetch framework ID from etcd: %v", err) } log.V(1).Infoln("constructing mesos scheduler driver") drv, err = bindings.NewMesosSchedulerDriver(*dconfig) if err != nil { return nil, fmt.Errorf("failed to construct scheduler driver: %v", err) } log.V(1).Infoln("constructed mesos scheduler driver:", drv) s.setDriver(drv) return drv, nil }) return schedulerProcess, driverFactory, etcdClient, eid }
func (s *SchedulerServer) bootstrap(hks hyperkube.Interface, sc *schedcfg.Config) (*ha.SchedulerProcess, ha.DriverFactory, *etcd.Client, *mesos.ExecutorID) { s.frameworkName = strings.TrimSpace(s.frameworkName) if s.frameworkName == "" { log.Fatalf("framework-name must be a non-empty string") } s.frameworkWebURI = strings.TrimSpace(s.frameworkWebURI) metrics.Register() runtime.Register() s.mux.Handle("/metrics", prometheus.Handler()) healthz.InstallHandler(s.mux) if (s.etcdConfigFile != "" && len(s.etcdServerList) != 0) || (s.etcdConfigFile == "" && len(s.etcdServerList) == 0) { log.Fatalf("specify either --etcd-servers or --etcd-config") } if len(s.apiServerList) < 1 { log.Fatal("No api servers specified.") } client, err := s.createAPIServerClient() if err != nil { log.Fatalf("Unable to make apiserver client: %v", err) } s.client = client if s.reconcileCooldown < defaultReconcileCooldown { s.reconcileCooldown = defaultReconcileCooldown log.Warningf("user-specified reconcile cooldown too small, defaulting to %v", s.reconcileCooldown) } eiPrototype, err := s.prepareExecutorInfo(hks) if err != nil { log.Fatalf("misconfigured executor: %v", err) } // TODO(jdef): remove the dependency on etcd as soon as // (1) the generic config store is available for the FrameworkId storage // (2) the generic master election is provided by the apiserver // Compare docs/proposals/high-availability.md etcdClient, err := newEtcd(s.etcdConfigFile, s.etcdServerList) if err != nil { log.Fatalf("misconfigured etcd: %v", err) } // mirror all nodes into the nodeStore var eiRegistry executorinfo.Registry nodesClient, err := s.createAPIServerClient() if err != nil { log.Fatalf("Cannot create client to watch nodes: %v", err) } nodeLW := cache.NewListWatchFromClient(nodesClient, "nodes", api.NamespaceAll, fields.Everything()) nodeStore, nodeCtl := controllerfw.NewInformer(nodeLW, &api.Node{}, s.nodeRelistPeriod, &controllerfw.ResourceEventHandlerFuncs{ DeleteFunc: func(obj interface{}) { node := obj.(*api.Node) if eiRegistry != nil { log.V(2).Infof("deleting node %q from registry", node.Name) eiRegistry.Invalidate(node.Name) } }, }) lookupNode := func(hostName string) *api.Node { n, _, _ := nodeStore.GetByKey(hostName) // ignore error and return nil then if n == nil { return nil } return n.(*api.Node) } execInfoCache, err := executorinfo.NewCache(defaultExecutorInfoCacheSize) if err != nil { log.Fatalf("cannot create executorinfo cache: %v", err) } eiRegistry, err = executorinfo.NewRegistry(lookupNode, eiPrototype, execInfoCache) if err != nil { log.Fatalf("cannot create executorinfo registry: %v", err) } pr := podtask.NewDefaultProcurement(eiPrototype, eiRegistry) fcfs := podschedulers.NewFCFSPodScheduler(pr, lookupNode) framework := framework.New(framework.Config{ SchedulerConfig: *sc, Client: client, FailoverTimeout: s.failoverTimeout, ReconcileInterval: s.reconcileInterval, ReconcileCooldown: s.reconcileCooldown, LookupNode: lookupNode, StoreFrameworkId: func(id string) { // TODO(jdef): port FrameworkId store to generic Kubernetes config store as soon as available _, err := etcdClient.Set(meta.FrameworkIDKey, id, uint64(s.failoverTimeout)) if err != nil { log.Errorf("failed to renew frameworkId TTL: %v", err) } }, ExecutorId: eiPrototype.GetExecutorId(), }) masterUri := s.mesosMaster info, cred, err := s.buildFrameworkInfo() if err != nil { log.Fatalf("Misconfigured mesos framework: %v", err) } schedulerProcess := ha.New(framework) dconfig := &bindings.DriverConfig{ Scheduler: schedulerProcess, Framework: info, Master: masterUri, Credential: cred, BindingAddress: s.address, BindingPort: uint16(s.driverPort), HostnameOverride: s.hostnameOverride, WithAuthContext: func(ctx context.Context) context.Context { ctx = auth.WithLoginProvider(ctx, s.mesosAuthProvider) ctx = sasl.WithBindingAddress(ctx, s.address) return ctx }, } // create event recorder sending events to the "" namespace of the apiserver broadcaster := record.NewBroadcaster() recorder := broadcaster.NewRecorder(api.EventSource{Component: "scheduler"}) broadcaster.StartRecordingToSink(client.Events("")) // create scheduler core with all components arranged around it lw := cache.NewListWatchFromClient(client, "pods", api.NamespaceAll, fields.Everything()) sched := components.New( sc, framework, fcfs, client, recorder, schedulerProcess.Terminal(), s.mux, lw, eiPrototype, s.mesosRoles, s.defaultContainerCPULimit, s.defaultContainerMemLimit, ) runtime.On(framework.Registration(), func() { sched.Run(schedulerProcess.Terminal()) }) runtime.On(framework.Registration(), s.newServiceWriter(schedulerProcess.Terminal())) runtime.On(framework.Registration(), func() { nodeCtl.Run(schedulerProcess.Terminal()) }) driverFactory := ha.DriverFactory(func() (drv bindings.SchedulerDriver, err error) { log.V(1).Infoln("performing deferred initialization") if err = framework.Init(sched, schedulerProcess.Master(), s.mux); err != nil { return nil, fmt.Errorf("failed to initialize pod scheduler: %v", err) } log.V(1).Infoln("deferred init complete") // defer obtaining framework ID to prevent multiple schedulers // from overwriting each other's framework IDs dconfig.Framework.Id, err = s.fetchFrameworkID(etcdClient) if err != nil { return nil, fmt.Errorf("failed to fetch framework ID from etcd: %v", err) } log.V(1).Infoln("constructing mesos scheduler driver") drv, err = bindings.NewMesosSchedulerDriver(*dconfig) if err != nil { return nil, fmt.Errorf("failed to construct scheduler driver: %v", err) } log.V(1).Infoln("constructed mesos scheduler driver:", drv) s.setDriver(drv) return drv, nil }) return schedulerProcess, driverFactory, etcdClient, eiPrototype.GetExecutorId() }
func newLifecycleTest(t *testing.T) lifecycleTest { assert := &EventAssertions{*assert.New(t)} // create a fake pod watch. We use that below to submit new pods to the scheduler podsListWatch := NewMockPodsListWatch(api.PodList{}) // create fake apiserver apiServer := NewTestServer(t, api.NamespaceDefault, podsListWatch) // create executor with some data for static pods if set executor := mesosutil.NewExecutorInfo( mesosutil.NewExecutorID("executor-id"), mesosutil.NewCommandInfo("executor-cmd"), ) executor.Data = []byte{0, 1, 2} // create scheduler strategy := NewAllocationStrategy( podtask.NewDefaultPredicate( mresource.DefaultDefaultContainerCPULimit, mresource.DefaultDefaultContainerMemLimit, ), podtask.NewDefaultProcurement( mresource.DefaultDefaultContainerCPULimit, mresource.DefaultDefaultContainerMemLimit, ), ) scheduler := New(Config{ Executor: executor, Client: client.NewOrDie(&client.Config{ Host: apiServer.server.URL, Version: testapi.Default.Version(), }), Scheduler: NewFCFSPodScheduler(strategy, apiServer.LookupNode), Schedcfg: *schedcfg.CreateDefaultConfig(), LookupNode: apiServer.LookupNode, }) assert.NotNil(scheduler.client, "client is nil") assert.NotNil(scheduler.executor, "executor is nil") assert.NotNil(scheduler.offers, "offer registry is nil") // create scheduler process schedulerProc := ha.New(scheduler) // get plugin config from it config := scheduler.NewPluginConfig( schedulerProc.Terminal(), http.DefaultServeMux, &podsListWatch.ListWatch, ) assert.NotNil(config) // make events observable eventObs := NewEventObserver() config.Recorder = eventObs // create plugin plugin := NewPlugin(config).(*schedulingPlugin) assert.NotNil(plugin) // create mock mesos scheduler driver driver := &joinableDriver{} return lifecycleTest{ apiServer: apiServer, driver: driver, eventObs: eventObs, plugin: plugin, podsListWatch: podsListWatch, scheduler: scheduler, schedulerProc: schedulerProc, t: t, } }