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
a90c7951
Commit
a90c7951
authored
Mar 02, 2017
by
David Ashpole
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add volume timestamps
parent
c5faf1c1
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
41 additions
and
17 deletions
+41
-17
types.go
pkg/kubelet/api/v1alpha1/stats/types.go
+1
-1
summary.go
pkg/kubelet/server/stats/summary.go
+4
-4
volume_stat_calculator.go
pkg/kubelet/server/stats/volume_stat_calculator.go
+2
-2
BUILD
pkg/volume/BUILD
+4
-3
metrics_du.go
pkg/volume/metrics_du.go
+2
-1
metrics_du_test.go
pkg/volume/metrics_du_test.go
+2
-2
metrics_statfs.go
pkg/volume/metrics_statfs.go
+2
-1
metrics_statfs_test.go
pkg/volume/metrics_statfs_test.go
+5
-3
testing.go
pkg/volume/testing/testing.go
+10
-0
volume.go
pkg/volume/volume.go
+4
-0
summary_test.go
test/e2e_node/summary_test.go
+5
-0
No files found.
pkg/kubelet/api/v1alpha1/stats/types.go
View file @
a90c7951
...
@@ -192,7 +192,7 @@ type VolumeStats struct {
...
@@ -192,7 +192,7 @@ type VolumeStats struct {
// FsStats contains data about filesystem usage.
// FsStats contains data about filesystem usage.
type
FsStats
struct
{
type
FsStats
struct
{
// The time at which these stats were updated.
// The time at which these stats were updated.
Time
unversioned
.
Time
`json:"time"`
Time
metav1
.
Time
`json:"time"`
// AvailableBytes represents the storage space available (bytes) for the filesystem.
// AvailableBytes represents the storage space available (bytes) for the filesystem.
// +optional
// +optional
AvailableBytes
*
uint64
`json:"availableBytes,omitempty"`
AvailableBytes
*
uint64
`json:"availableBytes,omitempty"`
...
...
pkg/kubelet/server/stats/summary.go
View file @
a90c7951
...
@@ -135,7 +135,7 @@ func (sb *summaryBuilder) build() (*stats.Summary, error) {
...
@@ -135,7 +135,7 @@ func (sb *summaryBuilder) build() (*stats.Summary, error) {
Memory
:
rootStats
.
Memory
,
Memory
:
rootStats
.
Memory
,
Network
:
sb
.
containerInfoV2ToNetworkStats
(
"node:"
+
sb
.
node
.
Name
,
&
rootInfo
),
Network
:
sb
.
containerInfoV2ToNetworkStats
(
"node:"
+
sb
.
node
.
Name
,
&
rootInfo
),
Fs
:
&
stats
.
FsStats
{
Fs
:
&
stats
.
FsStats
{
Time
:
unversioned
.
NewTime
(
cStats
.
Timestamp
),
Time
:
metav1
.
NewTime
(
cStats
.
Timestamp
),
AvailableBytes
:
&
sb
.
rootFsInfo
.
Available
,
AvailableBytes
:
&
sb
.
rootFsInfo
.
Available
,
CapacityBytes
:
&
sb
.
rootFsInfo
.
Capacity
,
CapacityBytes
:
&
sb
.
rootFsInfo
.
Capacity
,
UsedBytes
:
&
sb
.
rootFsInfo
.
Usage
,
UsedBytes
:
&
sb
.
rootFsInfo
.
Usage
,
...
@@ -146,7 +146,7 @@ func (sb *summaryBuilder) build() (*stats.Summary, error) {
...
@@ -146,7 +146,7 @@ func (sb *summaryBuilder) build() (*stats.Summary, error) {
StartTime
:
rootStats
.
StartTime
,
StartTime
:
rootStats
.
StartTime
,
Runtime
:
&
stats
.
RuntimeStats
{
Runtime
:
&
stats
.
RuntimeStats
{
ImageFs
:
&
stats
.
FsStats
{
ImageFs
:
&
stats
.
FsStats
{
Time
:
unversioned
.
NewTime
(
cStats
.
Timestamp
),
Time
:
metav1
.
NewTime
(
cStats
.
Timestamp
),
AvailableBytes
:
&
sb
.
imageFsInfo
.
Available
,
AvailableBytes
:
&
sb
.
imageFsInfo
.
Available
,
CapacityBytes
:
&
sb
.
imageFsInfo
.
Capacity
,
CapacityBytes
:
&
sb
.
imageFsInfo
.
Capacity
,
UsedBytes
:
&
sb
.
imageStats
.
TotalStorageBytes
,
UsedBytes
:
&
sb
.
imageStats
.
TotalStorageBytes
,
...
@@ -191,7 +191,7 @@ func (sb *summaryBuilder) containerInfoV2FsStats(
...
@@ -191,7 +191,7 @@ func (sb *summaryBuilder) containerInfoV2FsStats(
// The container logs live on the node rootfs device
// The container logs live on the node rootfs device
cs
.
Logs
=
&
stats
.
FsStats
{
cs
.
Logs
=
&
stats
.
FsStats
{
Time
:
unversioned
.
NewTime
(
lcs
.
Timestamp
),
Time
:
metav1
.
NewTime
(
lcs
.
Timestamp
),
AvailableBytes
:
&
sb
.
rootFsInfo
.
Available
,
AvailableBytes
:
&
sb
.
rootFsInfo
.
Available
,
CapacityBytes
:
&
sb
.
rootFsInfo
.
Capacity
,
CapacityBytes
:
&
sb
.
rootFsInfo
.
Capacity
,
InodesFree
:
sb
.
rootFsInfo
.
InodesFree
,
InodesFree
:
sb
.
rootFsInfo
.
InodesFree
,
...
@@ -205,7 +205,7 @@ func (sb *summaryBuilder) containerInfoV2FsStats(
...
@@ -205,7 +205,7 @@ func (sb *summaryBuilder) containerInfoV2FsStats(
// The container rootFs lives on the imageFs devices (which may not be the node root fs)
// The container rootFs lives on the imageFs devices (which may not be the node root fs)
cs
.
Rootfs
=
&
stats
.
FsStats
{
cs
.
Rootfs
=
&
stats
.
FsStats
{
Time
:
unversioned
.
NewTime
(
lcs
.
Timestamp
),
Time
:
metav1
.
NewTime
(
lcs
.
Timestamp
),
AvailableBytes
:
&
sb
.
imageFsInfo
.
Available
,
AvailableBytes
:
&
sb
.
imageFsInfo
.
Available
,
CapacityBytes
:
&
sb
.
imageFsInfo
.
Capacity
,
CapacityBytes
:
&
sb
.
imageFsInfo
.
Capacity
,
InodesFree
:
sb
.
imageFsInfo
.
InodesFree
,
InodesFree
:
sb
.
imageFsInfo
.
InodesFree
,
...
...
pkg/kubelet/server/stats/volume_stat_calculator.go
View file @
a90c7951
...
@@ -120,7 +120,7 @@ func (s *volumeStatCalculator) parsePodVolumeStats(podName string, metric *volum
...
@@ -120,7 +120,7 @@ func (s *volumeStatCalculator) parsePodVolumeStats(podName string, metric *volum
inodesUsed
:=
uint64
(
metric
.
InodesUsed
.
Value
())
inodesUsed
:=
uint64
(
metric
.
InodesUsed
.
Value
())
return
stats
.
VolumeStats
{
return
stats
.
VolumeStats
{
Name
:
podName
,
Name
:
podName
,
FsStats
:
stats
.
FsStats
{
AvailableBytes
:
&
available
,
CapacityBytes
:
&
capacity
,
UsedBytes
:
&
used
,
FsStats
:
stats
.
FsStats
{
Time
:
metric
.
Time
,
AvailableBytes
:
&
available
,
CapacityBytes
:
&
capacity
,
Inodes
:
&
inodes
,
InodesFree
:
&
inodesFree
,
InodesUsed
:
&
inodesUsed
},
UsedBytes
:
&
used
,
Inodes
:
&
inodes
,
InodesFree
:
&
inodesFree
,
InodesUsed
:
&
inodesUsed
},
}
}
}
}
pkg/volume/BUILD
View file @
a90c7951
...
@@ -50,7 +50,6 @@ go_test(
...
@@ -50,7 +50,6 @@ go_test(
name = "go_default_test",
name = "go_default_test",
srcs = [
srcs = [
"metrics_nil_test.go",
"metrics_nil_test.go",
"metrics_statfs_test.go",
"plugins_test.go",
"plugins_test.go",
"util_test.go",
"util_test.go",
],
],
...
@@ -66,13 +65,15 @@ go_test(
...
@@ -66,13 +65,15 @@ go_test(
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/watch",
"//vendor:k8s.io/apimachinery/pkg/watch",
"//vendor:k8s.io/client-go/util/testing",
],
],
)
)
go_test(
go_test(
name = "go_default_xtest",
name = "go_default_xtest",
srcs = ["metrics_du_test.go"],
srcs = [
"metrics_du_test.go",
"metrics_statfs_test.go",
],
tags = ["automanaged"],
tags = ["automanaged"],
deps = [
deps = [
"//pkg/volume:go_default_library",
"//pkg/volume:go_default_library",
...
...
pkg/volume/metrics_du.go
View file @
a90c7951
...
@@ -18,6 +18,7 @@ package volume
...
@@ -18,6 +18,7 @@ package volume
import
(
import
(
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/api/resource"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/volume/util"
"k8s.io/kubernetes/pkg/volume/util"
)
)
...
@@ -40,7 +41,7 @@ func NewMetricsDu(path string) MetricsProvider {
...
@@ -40,7 +41,7 @@ func NewMetricsDu(path string) MetricsProvider {
// and gathering filesystem info for the Volume path.
// and gathering filesystem info for the Volume path.
// See MetricsProvider.GetMetrics
// See MetricsProvider.GetMetrics
func
(
md
*
metricsDu
)
GetMetrics
()
(
*
Metrics
,
error
)
{
func
(
md
*
metricsDu
)
GetMetrics
()
(
*
Metrics
,
error
)
{
metrics
:=
&
Metrics
{}
metrics
:=
&
Metrics
{
Time
:
metav1
.
Now
()
}
if
md
.
path
==
""
{
if
md
.
path
==
""
{
return
metrics
,
NewNoPathDefinedError
()
return
metrics
,
NewNoPathDefinedError
()
}
}
...
...
pkg/volume/metrics_du_test.go
View file @
a90c7951
...
@@ -80,7 +80,7 @@ func TestMetricsDuRequirePath(t *testing.T) {
...
@@ -80,7 +80,7 @@ func TestMetricsDuRequirePath(t *testing.T) {
metrics
:=
NewMetricsDu
(
""
)
metrics
:=
NewMetricsDu
(
""
)
actual
,
err
:=
metrics
.
GetMetrics
()
actual
,
err
:=
metrics
.
GetMetrics
()
expected
:=
&
Metrics
{}
expected
:=
&
Metrics
{}
if
*
actual
!=
*
expected
{
if
!
volumetest
.
MetricsEqualIgnoreTimestamp
(
actual
,
expected
)
{
t
.
Errorf
(
"Expected empty Metrics from uninitialized MetricsDu, actual %v"
,
*
actual
)
t
.
Errorf
(
"Expected empty Metrics from uninitialized MetricsDu, actual %v"
,
*
actual
)
}
}
if
err
==
nil
{
if
err
==
nil
{
...
@@ -94,7 +94,7 @@ func TestMetricsDuRequireRealDirectory(t *testing.T) {
...
@@ -94,7 +94,7 @@ func TestMetricsDuRequireRealDirectory(t *testing.T) {
metrics
:=
NewMetricsDu
(
"/not/a/real/directory"
)
metrics
:=
NewMetricsDu
(
"/not/a/real/directory"
)
actual
,
err
:=
metrics
.
GetMetrics
()
actual
,
err
:=
metrics
.
GetMetrics
()
expected
:=
&
Metrics
{}
expected
:=
&
Metrics
{}
if
*
actual
!=
*
expected
{
if
!
volumetest
.
MetricsEqualIgnoreTimestamp
(
actual
,
expected
)
{
t
.
Errorf
(
"Expected empty Metrics from incorrectly initialized MetricsDu, actual %v"
,
*
actual
)
t
.
Errorf
(
"Expected empty Metrics from incorrectly initialized MetricsDu, actual %v"
,
*
actual
)
}
}
if
err
==
nil
{
if
err
==
nil
{
...
...
pkg/volume/metrics_statfs.go
View file @
a90c7951
...
@@ -18,6 +18,7 @@ package volume
...
@@ -18,6 +18,7 @@ package volume
import
(
import
(
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/api/resource"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/volume/util"
"k8s.io/kubernetes/pkg/volume/util"
)
)
...
@@ -39,7 +40,7 @@ func NewMetricsStatFS(path string) MetricsProvider {
...
@@ -39,7 +40,7 @@ func NewMetricsStatFS(path string) MetricsProvider {
// GetMetrics calculates the volume usage and device free space by executing "du"
// GetMetrics calculates the volume usage and device free space by executing "du"
// and gathering filesystem info for the Volume path.
// and gathering filesystem info for the Volume path.
func
(
md
*
metricsStatFS
)
GetMetrics
()
(
*
Metrics
,
error
)
{
func
(
md
*
metricsStatFS
)
GetMetrics
()
(
*
Metrics
,
error
)
{
metrics
:=
&
Metrics
{}
metrics
:=
&
Metrics
{
Time
:
metav1
.
Now
()
}
if
md
.
path
==
""
{
if
md
.
path
==
""
{
return
metrics
,
NewNoPathDefinedError
()
return
metrics
,
NewNoPathDefinedError
()
}
}
...
...
pkg/volume/metrics_statfs_test.go
View file @
a90c7951
...
@@ -14,20 +14,22 @@ See the License for the specific language governing permissions and
...
@@ -14,20 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
*/
*/
package
volume
package
volume
_test
import
(
import
(
"os"
"os"
"testing"
"testing"
utiltesting
"k8s.io/client-go/util/testing"
utiltesting
"k8s.io/client-go/util/testing"
.
"k8s.io/kubernetes/pkg/volume"
volumetest
"k8s.io/kubernetes/pkg/volume/testing"
)
)
func
TestGetMetricsStatFS
(
t
*
testing
.
T
)
{
func
TestGetMetricsStatFS
(
t
*
testing
.
T
)
{
metrics
:=
NewMetricsStatFS
(
""
)
metrics
:=
NewMetricsStatFS
(
""
)
actual
,
err
:=
metrics
.
GetMetrics
()
actual
,
err
:=
metrics
.
GetMetrics
()
expected
:=
&
Metrics
{}
expected
:=
&
Metrics
{}
if
*
actual
!=
*
expected
{
if
!
volumetest
.
MetricsEqualIgnoreTimestamp
(
actual
,
expected
)
{
t
.
Errorf
(
"Expected empty Metrics from uninitialized MetricsStatFS, actual %v"
,
*
actual
)
t
.
Errorf
(
"Expected empty Metrics from uninitialized MetricsStatFS, actual %v"
,
*
actual
)
}
}
if
err
==
nil
{
if
err
==
nil
{
...
@@ -36,7 +38,7 @@ func TestGetMetricsStatFS(t *testing.T) {
...
@@ -36,7 +38,7 @@ func TestGetMetricsStatFS(t *testing.T) {
metrics
=
NewMetricsStatFS
(
"/not/a/real/directory"
)
metrics
=
NewMetricsStatFS
(
"/not/a/real/directory"
)
actual
,
err
=
metrics
.
GetMetrics
()
actual
,
err
=
metrics
.
GetMetrics
()
if
*
actual
!=
*
expected
{
if
!
volumetest
.
MetricsEqualIgnoreTimestamp
(
actual
,
expected
)
{
t
.
Errorf
(
"Expected empty Metrics from incorrectly initialized MetricsStatFS, actual %v"
,
*
actual
)
t
.
Errorf
(
"Expected empty Metrics from incorrectly initialized MetricsStatFS, actual %v"
,
*
actual
)
}
}
if
err
==
nil
{
if
err
==
nil
{
...
...
pkg/volume/testing/testing.go
View file @
a90c7951
...
@@ -750,3 +750,13 @@ func CreateTestPVC(capacity string, accessModes []v1.PersistentVolumeAccessMode)
...
@@ -750,3 +750,13 @@ func CreateTestPVC(capacity string, accessModes []v1.PersistentVolumeAccessMode)
}
}
return
&
claim
return
&
claim
}
}
func
MetricsEqualIgnoreTimestamp
(
a
*
Metrics
,
b
*
Metrics
)
bool
{
available
:=
a
.
Available
==
b
.
Available
capacity
:=
a
.
Capacity
==
b
.
Capacity
used
:=
a
.
Used
==
b
.
Used
inodes
:=
a
.
Inodes
==
b
.
Inodes
inodesFree
:=
a
.
InodesFree
==
b
.
InodesFree
inodesUsed
:=
a
.
InodesUsed
==
b
.
InodesUsed
return
available
&&
capacity
&&
used
&&
inodes
&&
inodesFree
&&
inodesUsed
}
pkg/volume/volume.go
View file @
a90c7951
...
@@ -26,6 +26,7 @@ import (
...
@@ -26,6 +26,7 @@ import (
"github.com/golang/glog"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/api/resource"
metav1
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/v1"
)
)
...
@@ -52,6 +53,9 @@ type MetricsProvider interface {
...
@@ -52,6 +53,9 @@ type MetricsProvider interface {
// Metrics represents the used and available bytes of the Volume.
// Metrics represents the used and available bytes of the Volume.
type
Metrics
struct
{
type
Metrics
struct
{
// The time at which these stats were updated.
Time
metav1
.
Time
// Used represents the total bytes used by the Volume.
// Used represents the total bytes used by the Volume.
// Note: For block devices this maybe more than the total size of the files.
// Note: For block devices this maybe more than the total size of the files.
Used
*
resource
.
Quantity
Used
*
resource
.
Quantity
...
...
test/e2e_node/summary_test.go
View file @
a90c7951
...
@@ -116,6 +116,7 @@ var _ = framework.KubeDescribe("Summary API", func() {
...
@@ -116,6 +116,7 @@ var _ = framework.KubeDescribe("Summary API", func() {
"MajorPageFaults"
:
bounded
(
0
,
10
),
"MajorPageFaults"
:
bounded
(
0
,
10
),
}),
}),
"Rootfs"
:
ptrMatchAllFields
(
gstruct
.
Fields
{
"Rootfs"
:
ptrMatchAllFields
(
gstruct
.
Fields
{
"Time"
:
recent
(
maxStatsAge
),
"AvailableBytes"
:
fsCapacityBounds
,
"AvailableBytes"
:
fsCapacityBounds
,
"CapacityBytes"
:
fsCapacityBounds
,
"CapacityBytes"
:
fsCapacityBounds
,
"UsedBytes"
:
bounded
(
kb
,
10
*
mb
),
"UsedBytes"
:
bounded
(
kb
,
10
*
mb
),
...
@@ -124,6 +125,7 @@ var _ = framework.KubeDescribe("Summary API", func() {
...
@@ -124,6 +125,7 @@ var _ = framework.KubeDescribe("Summary API", func() {
"InodesUsed"
:
bounded
(
0
,
1E8
),
"InodesUsed"
:
bounded
(
0
,
1E8
),
}),
}),
"Logs"
:
ptrMatchAllFields
(
gstruct
.
Fields
{
"Logs"
:
ptrMatchAllFields
(
gstruct
.
Fields
{
"Time"
:
recent
(
maxStatsAge
),
"AvailableBytes"
:
fsCapacityBounds
,
"AvailableBytes"
:
fsCapacityBounds
,
"CapacityBytes"
:
fsCapacityBounds
,
"CapacityBytes"
:
fsCapacityBounds
,
"UsedBytes"
:
bounded
(
kb
,
10
*
mb
),
"UsedBytes"
:
bounded
(
kb
,
10
*
mb
),
...
@@ -145,6 +147,7 @@ var _ = framework.KubeDescribe("Summary API", func() {
...
@@ -145,6 +147,7 @@ var _ = framework.KubeDescribe("Summary API", func() {
"test-empty-dir"
:
gstruct
.
MatchAllFields
(
gstruct
.
Fields
{
"test-empty-dir"
:
gstruct
.
MatchAllFields
(
gstruct
.
Fields
{
"Name"
:
Equal
(
"test-empty-dir"
),
"Name"
:
Equal
(
"test-empty-dir"
),
"FsStats"
:
gstruct
.
MatchAllFields
(
gstruct
.
Fields
{
"FsStats"
:
gstruct
.
MatchAllFields
(
gstruct
.
Fields
{
"Time"
:
recent
(
maxStatsAge
),
"AvailableBytes"
:
fsCapacityBounds
,
"AvailableBytes"
:
fsCapacityBounds
,
"CapacityBytes"
:
fsCapacityBounds
,
"CapacityBytes"
:
fsCapacityBounds
,
"UsedBytes"
:
bounded
(
kb
,
1
*
mb
),
"UsedBytes"
:
bounded
(
kb
,
1
*
mb
),
...
@@ -183,6 +186,7 @@ var _ = framework.KubeDescribe("Summary API", func() {
...
@@ -183,6 +186,7 @@ var _ = framework.KubeDescribe("Summary API", func() {
"TxErrors"
:
bounded
(
0
,
100000
),
"TxErrors"
:
bounded
(
0
,
100000
),
})),
})),
"Fs"
:
ptrMatchAllFields
(
gstruct
.
Fields
{
"Fs"
:
ptrMatchAllFields
(
gstruct
.
Fields
{
"Time"
:
recent
(
maxStatsAge
),
"AvailableBytes"
:
fsCapacityBounds
,
"AvailableBytes"
:
fsCapacityBounds
,
"CapacityBytes"
:
fsCapacityBounds
,
"CapacityBytes"
:
fsCapacityBounds
,
"UsedBytes"
:
bounded
(
kb
,
10
*
gb
),
"UsedBytes"
:
bounded
(
kb
,
10
*
gb
),
...
@@ -192,6 +196,7 @@ var _ = framework.KubeDescribe("Summary API", func() {
...
@@ -192,6 +196,7 @@ var _ = framework.KubeDescribe("Summary API", func() {
}),
}),
"Runtime"
:
ptrMatchAllFields
(
gstruct
.
Fields
{
"Runtime"
:
ptrMatchAllFields
(
gstruct
.
Fields
{
"ImageFs"
:
ptrMatchAllFields
(
gstruct
.
Fields
{
"ImageFs"
:
ptrMatchAllFields
(
gstruct
.
Fields
{
"Time"
:
recent
(
maxStatsAge
),
"AvailableBytes"
:
fsCapacityBounds
,
"AvailableBytes"
:
fsCapacityBounds
,
"CapacityBytes"
:
fsCapacityBounds
,
"CapacityBytes"
:
fsCapacityBounds
,
"UsedBytes"
:
bounded
(
kb
,
10
*
gb
),
"UsedBytes"
:
bounded
(
kb
,
10
*
gb
),
...
...
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