Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
06d31c3b
Commit
06d31c3b
authored
Jan 30, 2019
by
wangqingcan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add goroutine to move unschedulablepods to activeq regularly
parent
6260c81b
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
180 additions
and
34 deletions
+180
-34
extender_test.go
pkg/scheduler/core/extender_test.go
+1
-1
generic_scheduler_test.go
pkg/scheduler/core/generic_scheduler_test.go
+5
-5
factory.go
pkg/scheduler/factory/factory.go
+1
-1
BUILD
pkg/scheduler/internal/queue/BUILD
+1
-0
scheduling_queue.go
pkg/scheduler/internal/queue/scheduling_queue.go
+42
-5
scheduling_queue_test.go
pkg/scheduler/internal/queue/scheduling_queue_test.go
+95
-22
BUILD
pkg/scheduler/util/BUILD
+1
-0
clock.go
pkg/scheduler/util/clock.go
+34
-0
No files found.
pkg/scheduler/core/extender_test.go
View file @
06d31c3b
...
...
@@ -507,7 +507,7 @@ func TestGenericSchedulerWithExtenders(t *testing.T) {
for
_
,
name
:=
range
test
.
nodes
{
cache
.
AddNode
(
createNode
(
name
))
}
queue
:=
internalqueue
.
NewSchedulingQueue
()
queue
:=
internalqueue
.
NewSchedulingQueue
(
nil
)
scheduler
:=
NewGenericScheduler
(
cache
,
nil
,
...
...
pkg/scheduler/core/generic_scheduler_test.go
View file @
06d31c3b
...
...
@@ -449,7 +449,7 @@ func TestGenericScheduler(t *testing.T) {
scheduler
:=
NewGenericScheduler
(
cache
,
nil
,
internalqueue
.
NewSchedulingQueue
(),
internalqueue
.
NewSchedulingQueue
(
nil
),
test
.
predicates
,
algorithm
.
EmptyPredicateMetadataProducer
,
test
.
prioritizers
,
...
...
@@ -485,7 +485,7 @@ func makeScheduler(predicates map[string]algorithm.FitPredicate, nodes []*v1.Nod
s
:=
NewGenericScheduler
(
cache
,
nil
,
internalqueue
.
NewSchedulingQueue
(),
internalqueue
.
NewSchedulingQueue
(
nil
),
predicates
,
algorithm
.
EmptyPredicateMetadataProducer
,
prioritizers
,
...
...
@@ -1412,7 +1412,7 @@ func TestPreempt(t *testing.T) {
scheduler
:=
NewGenericScheduler
(
cache
,
nil
,
internalqueue
.
NewSchedulingQueue
(),
internalqueue
.
NewSchedulingQueue
(
nil
),
map
[
string
]
algorithm
.
FitPredicate
{
"matches"
:
algorithmpredicates
.
PodFitsResources
},
algorithm
.
EmptyPredicateMetadataProducer
,
[]
algorithm
.
PriorityConfig
{{
Function
:
numericPriority
,
Weight
:
1
}},
...
...
@@ -1543,7 +1543,7 @@ func TestCacheInvalidationRace(t *testing.T) {
scheduler
:=
NewGenericScheduler
(
mockCache
,
eCache
,
internalqueue
.
NewSchedulingQueue
(),
internalqueue
.
NewSchedulingQueue
(
nil
),
ps
,
algorithm
.
EmptyPredicateMetadataProducer
,
prioritizers
,
...
...
@@ -1626,7 +1626,7 @@ func TestCacheInvalidationRace2(t *testing.T) {
scheduler
:=
NewGenericScheduler
(
cache
,
eCache
,
internalqueue
.
NewSchedulingQueue
(),
internalqueue
.
NewSchedulingQueue
(
nil
),
ps
,
algorithm
.
EmptyPredicateMetadataProducer
,
prioritizers
,
...
...
pkg/scheduler/factory/factory.go
View file @
06d31c3b
...
...
@@ -277,7 +277,7 @@ func NewConfigFactory(args *ConfigFactoryArgs) Configurator {
c
:=
&
configFactory
{
client
:
args
.
Client
,
podLister
:
schedulerCache
,
podQueue
:
internalqueue
.
NewSchedulingQueue
(),
podQueue
:
internalqueue
.
NewSchedulingQueue
(
stopEverything
),
nodeLister
:
args
.
NodeInformer
.
Lister
(),
pVLister
:
args
.
PvInformer
.
Lister
(),
pVCLister
:
args
.
PvcInformer
.
Lister
(),
...
...
pkg/scheduler/internal/queue/BUILD
View file @
06d31c3b
...
...
@@ -13,6 +13,7 @@ go_library(
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
...
...
pkg/scheduler/internal/queue/scheduling_queue.go
View file @
06d31c3b
...
...
@@ -31,12 +31,14 @@ import (
"fmt"
"reflect"
"sync"
"time"
"k8s.io/klog"
"k8s.io/api/core/v1"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
ktypes
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/cache"
podutil
"k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
...
...
@@ -48,6 +50,10 @@ var (
queueClosed
=
"scheduling queue is closed"
)
// If the pod stays in unschedulableQ longer than the unschedulableQTimeInterval,
// the pod will be moved from unschedulableQ to activeQ.
const
unschedulableQTimeInterval
=
60
*
time
.
Second
// SchedulingQueue is an interface for a queue to store pods waiting to be scheduled.
// The interface follows a pattern similar to cache.FIFO and cache.Heap and
// makes it easy to use those data structures as a SchedulingQueue.
...
...
@@ -79,9 +85,9 @@ type SchedulingQueue interface {
// NewSchedulingQueue initializes a new scheduling queue. If pod priority is
// enabled a priority queue is returned. If it is disabled, a FIFO is returned.
func
NewSchedulingQueue
()
SchedulingQueue
{
func
NewSchedulingQueue
(
stop
<-
chan
struct
{}
)
SchedulingQueue
{
if
util
.
PodPriorityEnabled
()
{
return
NewPriorityQueue
()
return
NewPriorityQueue
(
stop
)
}
return
NewFIFO
()
}
...
...
@@ -193,8 +199,10 @@ func NominatedNodeName(pod *v1.Pod) string {
// pods that are already tried and are determined to be unschedulable. The latter
// is called unschedulableQ.
type
PriorityQueue
struct
{
lock
sync
.
RWMutex
cond
sync
.
Cond
stop
<-
chan
struct
{}
clock
util
.
Clock
lock
sync
.
RWMutex
cond
sync
.
Cond
// activeQ is heap structure that scheduler actively looks at to find pods to
// schedule. Head of heap is the highest priority pod.
...
...
@@ -244,16 +252,25 @@ func activeQComp(pod1, pod2 interface{}) bool {
}
// NewPriorityQueue creates a PriorityQueue object.
func
NewPriorityQueue
()
*
PriorityQueue
{
func
NewPriorityQueue
(
stop
<-
chan
struct
{}
)
*
PriorityQueue
{
pq
:=
&
PriorityQueue
{
clock
:
util
.
RealClock
{},
stop
:
stop
,
activeQ
:
newHeap
(
cache
.
MetaNamespaceKeyFunc
,
activeQComp
),
unschedulableQ
:
newUnschedulablePodsMap
(),
nominatedPods
:
newNominatedPodMap
(),
}
pq
.
cond
.
L
=
&
pq
.
lock
pq
.
run
()
return
pq
}
// run starts the goroutine to pump from unschedulableQ to activeQ
func
(
p
*
PriorityQueue
)
run
()
{
go
wait
.
Until
(
p
.
flushUnschedulableQLeftover
,
30
*
time
.
Second
,
p
.
stop
)
}
// Add adds a pod to the active queue. It should be called only when a new pod
// is added so there is no chance the pod is already in either queue.
func
(
p
*
PriorityQueue
)
Add
(
pod
*
v1
.
Pod
)
error
{
...
...
@@ -324,6 +341,26 @@ func (p *PriorityQueue) AddUnschedulableIfNotPresent(pod *v1.Pod) error {
return
err
}
// flushUnschedulableQLeftover moves pod which stays in unschedulableQ longer than the durationStayUnschedulableQ
// to activeQ.
func
(
p
*
PriorityQueue
)
flushUnschedulableQLeftover
()
{
p
.
lock
.
Lock
()
defer
p
.
lock
.
Unlock
()
var
podsToMove
[]
*
v1
.
Pod
currentTime
:=
p
.
clock
.
Now
()
for
_
,
pod
:=
range
p
.
unschedulableQ
.
pods
{
lastScheduleTime
:=
podTimestamp
(
pod
)
if
!
lastScheduleTime
.
IsZero
()
&&
currentTime
.
Sub
(
lastScheduleTime
.
Time
)
>
unschedulableQTimeInterval
{
podsToMove
=
append
(
podsToMove
,
pod
)
}
}
if
len
(
podsToMove
)
>
0
{
p
.
movePodsToActiveQueue
(
podsToMove
)
}
}
// Pop removes the head of the active queue and returns it. It blocks if the
// activeQ is empty and waits until a new item is added to the queue. It also
// clears receivedMoveRequest to mark the beginning of a new scheduling cycle.
...
...
pkg/scheduler/internal/queue/scheduling_queue_test.go
View file @
06d31c3b
...
...
@@ -21,6 +21,7 @@ import (
"reflect"
"sync"
"testing"
"time"
"k8s.io/api/core/v1"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
...
...
@@ -94,8 +95,20 @@ var highPriorityPod, highPriNominatedPod, medPriorityPod, unschedulablePod = v1.
},
}
func
addOrUpdateUnschedulablePod
(
p
*
PriorityQueue
,
pod
*
v1
.
Pod
)
{
p
.
lock
.
Lock
()
defer
p
.
lock
.
Unlock
()
p
.
unschedulableQ
.
addOrUpdate
(
pod
)
}
func
getUnschedulablePod
(
p
*
PriorityQueue
,
pod
*
v1
.
Pod
)
*
v1
.
Pod
{
p
.
lock
.
Lock
()
defer
p
.
lock
.
Unlock
()
return
p
.
unschedulableQ
.
get
(
pod
)
}
func
TestPriorityQueue_Add
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
q
.
Add
(
&
medPriorityPod
)
q
.
Add
(
&
unschedulablePod
)
q
.
Add
(
&
highPriorityPod
)
...
...
@@ -126,8 +139,8 @@ func TestPriorityQueue_Add(t *testing.T) {
}
func
TestPriorityQueue_AddIfNotPresent
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
.
unschedulableQ
.
addOrUpdate
(
&
highPriNominatedPod
)
q
:=
NewPriorityQueue
(
nil
)
addOrUpdateUnschedulablePod
(
q
,
&
highPriNominatedPod
)
q
.
AddIfNotPresent
(
&
highPriNominatedPod
)
// Must not add anything.
q
.
AddIfNotPresent
(
&
medPriorityPod
)
q
.
AddIfNotPresent
(
&
unschedulablePod
)
...
...
@@ -152,13 +165,13 @@ func TestPriorityQueue_AddIfNotPresent(t *testing.T) {
if
len
(
q
.
nominatedPods
.
nominatedPods
[
"node1"
])
!=
2
{
t
.
Errorf
(
"Expected medPriorityPod and unschedulablePod to be still present in nomindatePods: %v"
,
q
.
nominatedPods
.
nominatedPods
[
"node1"
])
}
if
q
.
unschedulableQ
.
get
(
&
highPriNominatedPod
)
!=
&
highPriNominatedPod
{
if
getUnschedulablePod
(
q
,
&
highPriNominatedPod
)
!=
&
highPriNominatedPod
{
t
.
Errorf
(
"Pod %v was not found in the unschedulableQ."
,
highPriNominatedPod
.
Name
)
}
}
func
TestPriorityQueue_AddUnschedulableIfNotPresent
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
q
.
Add
(
&
highPriNominatedPod
)
q
.
AddUnschedulableIfNotPresent
(
&
highPriNominatedPod
)
// Must not add anything.
q
.
AddUnschedulableIfNotPresent
(
&
medPriorityPod
)
// This should go to activeQ.
...
...
@@ -185,13 +198,13 @@ func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) {
if
len
(
q
.
nominatedPods
.
nominatedPods
)
!=
1
{
t
.
Errorf
(
"Expected nomindatePods to have one element: %v"
,
q
.
nominatedPods
)
}
if
q
.
unschedulableQ
.
get
(
&
unschedulablePod
)
!=
&
unschedulablePod
{
if
getUnschedulablePod
(
q
,
&
unschedulablePod
)
!=
&
unschedulablePod
{
t
.
Errorf
(
"Pod %v was not found in the unschedulableQ."
,
unschedulablePod
.
Name
)
}
}
func
TestPriorityQueue_Pop
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
wg
:=
sync
.
WaitGroup
{}
wg
.
Add
(
1
)
go
func
()
{
...
...
@@ -208,7 +221,7 @@ func TestPriorityQueue_Pop(t *testing.T) {
}
func
TestPriorityQueue_Update
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
q
.
Update
(
nil
,
&
highPriorityPod
)
if
_
,
exists
,
_
:=
q
.
activeQ
.
Get
(
&
highPriorityPod
);
!
exists
{
t
.
Errorf
(
"Expected %v to be added to activeQ."
,
highPriorityPod
.
Name
)
...
...
@@ -244,7 +257,7 @@ func TestPriorityQueue_Update(t *testing.T) {
}
func
TestPriorityQueue_Delete
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
q
.
Update
(
&
highPriorityPod
,
&
highPriNominatedPod
)
q
.
Add
(
&
unschedulablePod
)
q
.
Delete
(
&
highPriNominatedPod
)
...
...
@@ -266,10 +279,10 @@ func TestPriorityQueue_Delete(t *testing.T) {
}
func
TestPriorityQueue_MoveAllToActiveQueue
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
q
.
Add
(
&
medPriorityPod
)
q
.
unschedulableQ
.
addOrUpdate
(
&
unschedulablePod
)
q
.
unschedulableQ
.
addOrUpdate
(
&
highPriorityPod
)
addOrUpdateUnschedulablePod
(
q
,
&
unschedulablePod
)
addOrUpdateUnschedulablePod
(
q
,
&
highPriorityPod
)
q
.
MoveAllToActiveQueue
()
if
q
.
activeQ
.
data
.
Len
()
!=
3
{
t
.
Error
(
"Expected all items to be in activeQ."
)
...
...
@@ -312,28 +325,28 @@ func TestPriorityQueue_AssignedPodAdded(t *testing.T) {
Spec
:
v1
.
PodSpec
{
NodeName
:
"machine1"
},
}
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
q
.
Add
(
&
medPriorityPod
)
// Add a couple of pods to the unschedulableQ.
q
.
unschedulableQ
.
addOrUpdate
(
&
unschedulablePod
)
q
.
unschedulableQ
.
addOrUpdate
(
affinityPod
)
addOrUpdateUnschedulablePod
(
q
,
&
unschedulablePod
)
addOrUpdateUnschedulablePod
(
q
,
affinityPod
)
// Simulate addition of an assigned pod. The pod has matching labels for
// affinityPod. So, affinityPod should go to activeQ.
q
.
AssignedPodAdded
(
&
labelPod
)
if
q
.
unschedulableQ
.
get
(
affinityPod
)
!=
nil
{
if
getUnschedulablePod
(
q
,
affinityPod
)
!=
nil
{
t
.
Error
(
"affinityPod is still in the unschedulableQ."
)
}
if
_
,
exists
,
_
:=
q
.
activeQ
.
Get
(
affinityPod
);
!
exists
{
t
.
Error
(
"affinityPod is not moved to activeQ."
)
}
// Check that the other pod is still in the unschedulableQ.
if
q
.
unschedulableQ
.
get
(
&
unschedulablePod
)
==
nil
{
if
getUnschedulablePod
(
q
,
&
unschedulablePod
)
==
nil
{
t
.
Error
(
"unschedulablePod is not in the unschedulableQ."
)
}
}
func
TestPriorityQueue_NominatedPodsForNode
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
q
.
Add
(
&
medPriorityPod
)
q
.
Add
(
&
unschedulablePod
)
q
.
Add
(
&
highPriorityPod
)
...
...
@@ -350,7 +363,7 @@ func TestPriorityQueue_NominatedPodsForNode(t *testing.T) {
}
func
TestPriorityQueue_UpdateNominatedPodForNode
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
if
err
:=
q
.
Add
(
&
medPriorityPod
);
err
!=
nil
{
t
.
Errorf
(
"add failed: %v"
,
err
)
}
...
...
@@ -581,7 +594,7 @@ func TestSchedulingQueue_Close(t *testing.T) {
},
{
name
:
"PriorityQueue close"
,
q
:
NewPriorityQueue
(),
q
:
NewPriorityQueue
(
nil
),
expectedErr
:
fmt
.
Errorf
(
queueClosed
),
},
}
...
...
@@ -610,7 +623,7 @@ func TestSchedulingQueue_Close(t *testing.T) {
// ensures that an unschedulable pod does not block head of the queue when there
// are frequent events that move pods to the active queue.
func
TestRecentlyTriedPodsGoBack
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
// Add a few pods to priority queue.
for
i
:=
0
;
i
<
5
;
i
++
{
p
:=
v1
.
Pod
{
...
...
@@ -664,7 +677,7 @@ func TestRecentlyTriedPodsGoBack(t *testing.T) {
// This behavior ensures that an unschedulable pod does not block head of the queue when there
// are frequent events that move pods to the active queue.
func
TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
()
q
:=
NewPriorityQueue
(
nil
)
// Add an unschedulable pod to a priority queue.
// This makes a situation that the pod was tried to schedule
...
...
@@ -746,3 +759,63 @@ func TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod(t *testing.T) {
t
.
Errorf
(
"Expected that test-newer-pod was popped, got %v"
,
p2
.
Name
)
}
}
// TestHighProirotyFlushUnschedulableQLeftover tests that pods will be moved to
// activeQ after one minutes if it is in unschedulableQ
func
TestHighProirotyFlushUnschedulableQLeftover
(
t
*
testing
.
T
)
{
q
:=
NewPriorityQueue
(
nil
)
midPod
:=
v1
.
Pod
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-midpod"
,
Namespace
:
"ns1"
,
UID
:
types
.
UID
(
"tp-mid"
),
},
Spec
:
v1
.
PodSpec
{
Priority
:
&
midPriority
,
},
Status
:
v1
.
PodStatus
{
NominatedNodeName
:
"node1"
,
},
}
highPod
:=
v1
.
Pod
{
ObjectMeta
:
metav1
.
ObjectMeta
{
Name
:
"test-highpod"
,
Namespace
:
"ns1"
,
UID
:
types
.
UID
(
"tp-high"
),
},
Spec
:
v1
.
PodSpec
{
Priority
:
&
highPriority
,
},
Status
:
v1
.
PodStatus
{
NominatedNodeName
:
"node1"
,
},
}
addOrUpdateUnschedulablePod
(
q
,
&
highPod
)
addOrUpdateUnschedulablePod
(
q
,
&
midPod
)
// Update pod condition to highPod.
podutil
.
UpdatePodCondition
(
&
highPod
.
Status
,
&
v1
.
PodCondition
{
Type
:
v1
.
PodScheduled
,
Status
:
v1
.
ConditionFalse
,
Reason
:
v1
.
PodReasonUnschedulable
,
Message
:
"fake scheduling failure"
,
LastProbeTime
:
metav1
.
Time
{
Time
:
time
.
Now
()
.
Add
(
-
1
*
unschedulableQTimeInterval
)},
})
// Update pod condition to midPod.
podutil
.
UpdatePodCondition
(
&
midPod
.
Status
,
&
v1
.
PodCondition
{
Type
:
v1
.
PodScheduled
,
Status
:
v1
.
ConditionFalse
,
Reason
:
v1
.
PodReasonUnschedulable
,
Message
:
"fake scheduling failure"
,
LastProbeTime
:
metav1
.
Time
{
Time
:
time
.
Now
()
.
Add
(
-
1
*
unschedulableQTimeInterval
)},
})
if
p
,
err
:=
q
.
Pop
();
err
!=
nil
||
p
!=
&
highPod
{
t
.
Errorf
(
"Expected: %v after Pop, but got: %v"
,
highPriorityPod
.
Name
,
p
.
Name
)
}
if
p
,
err
:=
q
.
Pop
();
err
!=
nil
||
p
!=
&
midPod
{
t
.
Errorf
(
"Expected: %v after Pop, but got: %v"
,
medPriorityPod
.
Name
,
p
.
Name
)
}
}
pkg/scheduler/util/BUILD
View file @
06d31c3b
...
...
@@ -25,6 +25,7 @@ go_library(
name = "go_default_library",
srcs = [
"backoff_utils.go",
"clock.go",
"utils.go",
],
importpath = "k8s.io/kubernetes/pkg/scheduler/util",
...
...
pkg/scheduler/util/clock.go
0 → 100644
View file @
06d31c3b
/*
Copyright 2018 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
util
import
(
"time"
)
// Clock provides an interface for getting the current time
type
Clock
interface
{
Now
()
time
.
Time
}
// RealClock implements a clock using time
type
RealClock
struct
{}
// Now returns the current time with time.Now
func
(
RealClock
)
Now
()
time
.
Time
{
return
time
.
Now
()
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment