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
153c57ff
Commit
153c57ff
authored
Sep 18, 2015
by
nikhiljindal
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Adding a DeploymentDescriber to kubectl
parent
ae81f0b5
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
184 additions
and
72 deletions
+184
-72
deployment_controller.go
pkg/controller/deployment/deployment_controller.go
+8
-70
describe.go
pkg/kubectl/describe.go
+46
-2
describe_test.go
pkg/kubectl/describe_test.go
+21
-0
deployment.go
pkg/util/deployment/deployment.go
+109
-0
No files found.
pkg/controller/deployment/deployment_controller.go
View file @
153c57ff
...
@@ -18,7 +18,6 @@ package deployment
...
@@ -18,7 +18,6 @@ package deployment
import
(
import
(
"fmt"
"fmt"
"hash/adler32"
"math"
"math"
"time"
"time"
...
@@ -28,6 +27,7 @@ import (
...
@@ -28,6 +27,7 @@ import (
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util"
deploymentUtil
"k8s.io/kubernetes/pkg/util/deployment"
)
)
type
DeploymentController
struct
{
type
DeploymentController
struct
{
...
@@ -116,59 +116,21 @@ func (d *DeploymentController) reconcileRollingUpdateDeployment(deployment exper
...
@@ -116,59 +116,21 @@ func (d *DeploymentController) reconcileRollingUpdateDeployment(deployment exper
}
}
func
(
d
*
DeploymentController
)
getOldRCs
(
deployment
experimental
.
Deployment
)
([]
*
api
.
ReplicationController
,
error
)
{
func
(
d
*
DeploymentController
)
getOldRCs
(
deployment
experimental
.
Deployment
)
([]
*
api
.
ReplicationController
,
error
)
{
namespace
:=
deployment
.
ObjectMeta
.
Namespace
return
deploymentUtil
.
GetOldRCs
(
deployment
,
d
.
client
)
// 1. Find all pods whose labels match deployment.Spec.Selector
podList
,
err
:=
d
.
client
.
Pods
(
namespace
)
.
List
(
labels
.
SelectorFromSet
(
deployment
.
Spec
.
Selector
),
fields
.
Everything
())
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"error listing pods: %v"
,
err
)
}
// 2. Find the corresponding RCs for pods in podList.
// TODO: Right now we list all RCs and then filter. We should add an API for this.
oldRCs
:=
map
[
string
]
api
.
ReplicationController
{}
rcList
,
err
:=
d
.
client
.
ReplicationControllers
(
namespace
)
.
List
(
labels
.
Everything
())
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"error listing replication controllers: %v"
,
err
)
}
for
_
,
pod
:=
range
podList
.
Items
{
podLabelsSelector
:=
labels
.
Set
(
pod
.
ObjectMeta
.
Labels
)
for
_
,
rc
:=
range
rcList
.
Items
{
rcLabelsSelector
:=
labels
.
SelectorFromSet
(
rc
.
Spec
.
Selector
)
if
rcLabelsSelector
.
Matches
(
podLabelsSelector
)
{
// Filter out RC that has the same pod template spec as the deployment - that is the new RC.
if
api
.
Semantic
.
DeepEqual
(
rc
.
Spec
.
Template
,
getNewRCTemplate
(
deployment
))
{
continue
}
oldRCs
[
rc
.
ObjectMeta
.
Name
]
=
rc
}
}
}
requiredRCs
:=
[]
*
api
.
ReplicationController
{}
for
_
,
value
:=
range
oldRCs
{
requiredRCs
=
append
(
requiredRCs
,
&
value
)
}
return
requiredRCs
,
nil
}
}
// Returns an RC that matches the intent of the given deployment.
// Returns an RC that matches the intent of the given deployment.
// It creates a new RC if required.
// It creates a new RC if required.
func
(
d
*
DeploymentController
)
getNewRC
(
deployment
experimental
.
Deployment
)
(
*
api
.
ReplicationController
,
error
)
{
func
(
d
*
DeploymentController
)
getNewRC
(
deployment
experimental
.
Deployment
)
(
*
api
.
ReplicationController
,
error
)
{
namespace
:=
deployment
.
ObjectMeta
.
Namespace
existingNewRC
,
err
:=
deploymentUtil
.
GetNewRC
(
deployment
,
d
.
client
)
// Find if the required RC exists already.
if
err
!=
nil
||
existingNewRC
!=
nil
{
rcList
,
err
:=
d
.
client
.
ReplicationControllers
(
namespace
)
.
List
(
labels
.
Everything
())
return
existingNewRC
,
err
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"error listing replication controllers: %v"
,
err
)
}
newRCTemplate
:=
getNewRCTemplate
(
deployment
)
for
_
,
rc
:=
range
rcList
.
Items
{
if
api
.
Semantic
.
DeepEqual
(
rc
.
Spec
.
Template
,
newRCTemplate
)
{
// This is the new RC.
return
&
rc
,
nil
}
}
}
// new RC does not exist, create one.
// new RC does not exist, create one.
podTemplateSpecHash
:=
getPodTemplateSpecHash
(
deployment
.
Spec
.
Template
)
namespace
:=
deployment
.
ObjectMeta
.
Namespace
podTemplateSpecHash
:=
deploymentUtil
.
GetPodTemplateSpecHash
(
deployment
.
Spec
.
Template
)
rcName
:=
fmt
.
Sprintf
(
"deploymentrc-%d"
,
podTemplateSpecHash
)
rcName
:=
fmt
.
Sprintf
(
"deploymentrc-%d"
,
podTemplateSpecHash
)
newRCTemplate
:=
deploymentUtil
.
GetNewRCTemplate
(
deployment
)
newRC
:=
api
.
ReplicationController
{
newRC
:=
api
.
ReplicationController
{
ObjectMeta
:
api
.
ObjectMeta
{
ObjectMeta
:
api
.
ObjectMeta
{
Name
:
rcName
,
Name
:
rcName
,
...
@@ -187,30 +149,6 @@ func (d *DeploymentController) getNewRC(deployment experimental.Deployment) (*ap
...
@@ -187,30 +149,6 @@ func (d *DeploymentController) getNewRC(deployment experimental.Deployment) (*ap
return
createdRC
,
nil
return
createdRC
,
nil
}
}
func
getNewRCTemplate
(
deployment
experimental
.
Deployment
)
*
api
.
PodTemplateSpec
{
// newRC will have the same template as in deployment spec, plus a unique label in some cases.
newRCTemplate
:=
&
api
.
PodTemplateSpec
{
ObjectMeta
:
deployment
.
Spec
.
Template
.
ObjectMeta
,
Spec
:
deployment
.
Spec
.
Template
.
Spec
,
}
podTemplateSpecHash
:=
getPodTemplateSpecHash
(
newRCTemplate
)
if
deployment
.
Spec
.
UniqueLabelKey
!=
""
{
newLabels
:=
map
[
string
]
string
{}
for
key
,
value
:=
range
deployment
.
Spec
.
Template
.
ObjectMeta
.
Labels
{
newLabels
[
key
]
=
value
}
newLabels
[
deployment
.
Spec
.
UniqueLabelKey
]
=
fmt
.
Sprintf
(
"%d"
,
podTemplateSpecHash
)
newRCTemplate
.
ObjectMeta
.
Labels
=
newLabels
}
return
newRCTemplate
}
func
getPodTemplateSpecHash
(
template
*
api
.
PodTemplateSpec
)
uint32
{
podTemplateSpecHasher
:=
adler32
.
New
()
util
.
DeepHashObject
(
podTemplateSpecHasher
,
template
)
return
podTemplateSpecHasher
.
Sum32
()
}
func
(
d
*
DeploymentController
)
getPodsForRCs
(
replicationControllers
[]
*
api
.
ReplicationController
)
([]
api
.
Pod
,
error
)
{
func
(
d
*
DeploymentController
)
getPodsForRCs
(
replicationControllers
[]
*
api
.
ReplicationController
)
([]
api
.
Pod
,
error
)
{
allPods
:=
[]
api
.
Pod
{}
allPods
:=
[]
api
.
Pod
{}
for
_
,
rc
:=
range
replicationControllers
{
for
_
,
rc
:=
range
replicationControllers
{
...
...
pkg/kubectl/describe.go
View file @
153c57ff
...
@@ -35,6 +35,7 @@ import (
...
@@ -35,6 +35,7 @@ import (
qosutil
"k8s.io/kubernetes/pkg/kubelet/qos/util"
qosutil
"k8s.io/kubernetes/pkg/kubelet/qos/util"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/types"
deploymentUtil
"k8s.io/kubernetes/pkg/util/deployment"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/sets"
)
)
...
@@ -88,6 +89,7 @@ func expDescriberMap(c *client.Client) map[string]Describer {
...
@@ -88,6 +89,7 @@ func expDescriberMap(c *client.Client) map[string]Describer {
"HorizontalPodAutoscaler"
:
&
HorizontalPodAutoscalerDescriber
{
c
},
"HorizontalPodAutoscaler"
:
&
HorizontalPodAutoscalerDescriber
{
c
},
"DaemonSet"
:
&
DaemonSetDescriber
{
c
},
"DaemonSet"
:
&
DaemonSetDescriber
{
c
},
"Job"
:
&
JobDescriber
{
c
},
"Job"
:
&
JobDescriber
{
c
},
"Deployment"
:
&
DeploymentDescriber
{
c
},
}
}
}
}
...
@@ -478,7 +480,11 @@ func describePod(pod *api.Pod, rcs []api.ReplicationController, events *api.Even
...
@@ -478,7 +480,11 @@ func describePod(pod *api.Pod, rcs []api.ReplicationController, events *api.Even
fmt
.
Fprintf
(
out
,
"Reason:
\t
%s
\n
"
,
pod
.
Status
.
Reason
)
fmt
.
Fprintf
(
out
,
"Reason:
\t
%s
\n
"
,
pod
.
Status
.
Reason
)
fmt
.
Fprintf
(
out
,
"Message:
\t
%s
\n
"
,
pod
.
Status
.
Message
)
fmt
.
Fprintf
(
out
,
"Message:
\t
%s
\n
"
,
pod
.
Status
.
Message
)
fmt
.
Fprintf
(
out
,
"IP:
\t
%s
\n
"
,
pod
.
Status
.
PodIP
)
fmt
.
Fprintf
(
out
,
"IP:
\t
%s
\n
"
,
pod
.
Status
.
PodIP
)
fmt
.
Fprintf
(
out
,
"Replication Controllers:
\t
%s
\n
"
,
printReplicationControllersByLabels
(
rcs
))
var
matchingRCs
[]
*
api
.
ReplicationController
for
_
,
rc
:=
range
rcs
{
matchingRCs
=
append
(
matchingRCs
,
&
rc
)
}
fmt
.
Fprintf
(
out
,
"Replication Controllers:
\t
%s
\n
"
,
printReplicationControllersByLabels
(
matchingRCs
))
fmt
.
Fprintf
(
out
,
"Containers:
\n
"
)
fmt
.
Fprintf
(
out
,
"Containers:
\n
"
)
describeContainers
(
pod
,
out
)
describeContainers
(
pod
,
out
)
if
len
(
pod
.
Status
.
Conditions
)
>
0
{
if
len
(
pod
.
Status
.
Conditions
)
>
0
{
...
@@ -1395,6 +1401,44 @@ func DescribeEvents(el *api.EventList, w io.Writer) {
...
@@ -1395,6 +1401,44 @@ func DescribeEvents(el *api.EventList, w io.Writer) {
}
}
}
}
// DeploymentDescriber generates information about a deployment.
type
DeploymentDescriber
struct
{
client
.
Interface
}
func
(
dd
*
DeploymentDescriber
)
Describe
(
namespace
,
name
string
)
(
string
,
error
)
{
d
,
err
:=
dd
.
Experimental
()
.
Deployments
(
namespace
)
.
Get
(
name
)
if
err
!=
nil
{
return
""
,
err
}
return
tabbedString
(
func
(
out
io
.
Writer
)
error
{
fmt
.
Fprintf
(
out
,
"Name:
\t
%s
\n
"
,
d
.
ObjectMeta
.
Name
)
fmt
.
Fprintf
(
out
,
"Namespace:
\t
%s
\n
"
,
d
.
ObjectMeta
.
Namespace
)
fmt
.
Fprintf
(
out
,
"CreationTimestamp:
\t
%s
\n
"
,
d
.
CreationTimestamp
.
Time
.
Format
(
time
.
RFC1123Z
))
fmt
.
Fprintf
(
out
,
"Labels:
\t
%s
\n
"
,
labels
.
FormatLabels
(
d
.
Labels
))
fmt
.
Fprintf
(
out
,
"Selector:
\t
%s
\n
"
,
labels
.
FormatLabels
(
d
.
Spec
.
Selector
))
fmt
.
Fprintf
(
out
,
"Replicas:
\t
%d updated / %d total
\n
"
,
d
.
Status
.
UpdatedReplicas
,
d
.
Spec
.
Replicas
)
fmt
.
Fprintf
(
out
,
"StrategyType:
\t
%s
\n
"
,
d
.
Spec
.
Strategy
.
Type
)
if
d
.
Spec
.
Strategy
.
RollingUpdate
!=
nil
{
ru
:=
d
.
Spec
.
Strategy
.
RollingUpdate
fmt
.
Fprintf
(
out
,
"RollingUpdateStrategy:
\t
%s max unavailable, %s max surge, %d min ready seconds
\n
"
,
ru
.
MaxUnavailable
.
String
(),
ru
.
MaxSurge
.
String
(),
ru
.
MinReadySeconds
)
}
oldRCs
,
err
:=
deploymentUtil
.
GetOldRCs
(
*
d
,
dd
)
if
err
==
nil
{
fmt
.
Fprintf
(
out
,
"OldReplicationControllers:
\t
%s
\n
"
,
printReplicationControllersByLabels
(
oldRCs
))
}
newRC
,
err
:=
deploymentUtil
.
GetNewRC
(
*
d
,
dd
)
if
err
==
nil
{
var
newRCs
[]
*
api
.
ReplicationController
if
newRC
!=
nil
{
newRCs
=
append
(
newRCs
,
newRC
)
}
fmt
.
Fprintf
(
out
,
"NewReplicationController:
\t
%s
\n
"
,
printReplicationControllersByLabels
(
newRCs
))
}
return
nil
})
}
// Get all daemon set whose selectors would match a given set of labels.
// Get all daemon set whose selectors would match a given set of labels.
// TODO: Move this to pkg/client and ideally implement it server-side (instead
// TODO: Move this to pkg/client and ideally implement it server-side (instead
// of getting all DS's and searching through them manually).
// of getting all DS's and searching through them manually).
...
@@ -1442,7 +1486,7 @@ func getReplicationControllersForLabels(c client.ReplicationControllerInterface,
...
@@ -1442,7 +1486,7 @@ func getReplicationControllersForLabels(c client.ReplicationControllerInterface,
return
matchingRCs
,
nil
return
matchingRCs
,
nil
}
}
func
printReplicationControllersByLabels
(
matchingRCs
[]
api
.
ReplicationController
)
string
{
func
printReplicationControllersByLabels
(
matchingRCs
[]
*
api
.
ReplicationController
)
string
{
// Format the matching RC's into strings.
// Format the matching RC's into strings.
var
rcStrings
[]
string
var
rcStrings
[]
string
for
_
,
controller
:=
range
matchingRCs
{
for
_
,
controller
:=
range
matchingRCs
{
...
...
pkg/kubectl/describe_test.go
View file @
153c57ff
...
@@ -27,6 +27,7 @@ import (
...
@@ -27,6 +27,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/experimental"
client
"k8s.io/kubernetes/pkg/client/unversioned"
client
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
)
)
...
@@ -476,6 +477,26 @@ func TestPersistentVolumeDescriber(t *testing.T) {
...
@@ -476,6 +477,26 @@ func TestPersistentVolumeDescriber(t *testing.T) {
if
str
==
""
{
if
str
==
""
{
t
.
Errorf
(
"Unexpected empty string for test %s. Expected PV Describer output"
,
name
)
t
.
Errorf
(
"Unexpected empty string for test %s. Expected PV Describer output"
,
name
)
}
}
}
}
func
TestDescribeDeployment
(
t
*
testing
.
T
)
{
fake
:=
testclient
.
NewSimpleFake
(
&
experimental
.
Deployment
{
ObjectMeta
:
api
.
ObjectMeta
{
Name
:
"bar"
,
Namespace
:
"foo"
,
},
Spec
:
experimental
.
DeploymentSpec
{
Template
:
&
api
.
PodTemplateSpec
{},
},
})
c
:=
&
describeClient
{
T
:
t
,
Namespace
:
"foo"
,
Interface
:
fake
}
d
:=
DeploymentDescriber
{
c
}
out
,
err
:=
d
.
Describe
(
"foo"
,
"bar"
)
if
err
!=
nil
{
t
.
Errorf
(
"unexpected error: %v"
,
err
)
}
if
!
strings
.
Contains
(
out
,
"bar"
)
||
!
strings
.
Contains
(
out
,
"foo"
)
{
t
.
Errorf
(
"unexpected out: %s"
,
out
)
}
}
}
}
pkg/util/deployment/deployment.go
0 → 100644
View file @
153c57ff
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
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
deployment
import
(
"fmt"
"hash/adler32"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/experimental"
client
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util"
)
// Returns the old RCs targetted by the given Deployment.
func
GetOldRCs
(
deployment
experimental
.
Deployment
,
c
client
.
Interface
)
([]
*
api
.
ReplicationController
,
error
)
{
namespace
:=
deployment
.
ObjectMeta
.
Namespace
// 1. Find all pods whose labels match deployment.Spec.Selector
podList
,
err
:=
c
.
Pods
(
namespace
)
.
List
(
labels
.
SelectorFromSet
(
deployment
.
Spec
.
Selector
),
fields
.
Everything
())
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"error listing pods: %v"
,
err
)
}
// 2. Find the corresponding RCs for pods in podList.
// TODO: Right now we list all RCs and then filter. We should add an API for this.
oldRCs
:=
map
[
string
]
api
.
ReplicationController
{}
rcList
,
err
:=
c
.
ReplicationControllers
(
namespace
)
.
List
(
labels
.
Everything
())
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"error listing replication controllers: %v"
,
err
)
}
for
_
,
pod
:=
range
podList
.
Items
{
podLabelsSelector
:=
labels
.
Set
(
pod
.
ObjectMeta
.
Labels
)
for
_
,
rc
:=
range
rcList
.
Items
{
rcLabelsSelector
:=
labels
.
SelectorFromSet
(
rc
.
Spec
.
Selector
)
if
rcLabelsSelector
.
Matches
(
podLabelsSelector
)
{
// Filter out RC that has the same pod template spec as the deployment - that is the new RC.
if
api
.
Semantic
.
DeepEqual
(
rc
.
Spec
.
Template
,
GetNewRCTemplate
(
deployment
))
{
continue
}
oldRCs
[
rc
.
ObjectMeta
.
Name
]
=
rc
}
}
}
requiredRCs
:=
[]
*
api
.
ReplicationController
{}
for
_
,
value
:=
range
oldRCs
{
requiredRCs
=
append
(
requiredRCs
,
&
value
)
}
return
requiredRCs
,
nil
}
// Returns an RC that matches the intent of the given deployment.
// Returns nil if the new RC doesnt exist yet.
func
GetNewRC
(
deployment
experimental
.
Deployment
,
c
client
.
Interface
)
(
*
api
.
ReplicationController
,
error
)
{
namespace
:=
deployment
.
ObjectMeta
.
Namespace
rcList
,
err
:=
c
.
ReplicationControllers
(
namespace
)
.
List
(
labels
.
Everything
())
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"error listing replication controllers: %v"
,
err
)
}
newRCTemplate
:=
GetNewRCTemplate
(
deployment
)
for
_
,
rc
:=
range
rcList
.
Items
{
if
api
.
Semantic
.
DeepEqual
(
rc
.
Spec
.
Template
,
newRCTemplate
)
{
// This is the new RC.
return
&
rc
,
nil
}
}
// new RC does not exist.
return
nil
,
nil
}
// Returns the desired PodTemplateSpec for the new RC corresponding to the given RC.
func
GetNewRCTemplate
(
deployment
experimental
.
Deployment
)
*
api
.
PodTemplateSpec
{
// newRC will have the same template as in deployment spec, plus a unique label in some cases.
newRCTemplate
:=
&
api
.
PodTemplateSpec
{
ObjectMeta
:
deployment
.
Spec
.
Template
.
ObjectMeta
,
Spec
:
deployment
.
Spec
.
Template
.
Spec
,
}
podTemplateSpecHash
:=
GetPodTemplateSpecHash
(
newRCTemplate
)
if
deployment
.
Spec
.
UniqueLabelKey
!=
""
{
newLabels
:=
map
[
string
]
string
{}
for
key
,
value
:=
range
deployment
.
Spec
.
Template
.
ObjectMeta
.
Labels
{
newLabels
[
key
]
=
value
}
newLabels
[
deployment
.
Spec
.
UniqueLabelKey
]
=
fmt
.
Sprintf
(
"%d"
,
podTemplateSpecHash
)
newRCTemplate
.
ObjectMeta
.
Labels
=
newLabels
}
return
newRCTemplate
}
func
GetPodTemplateSpecHash
(
template
*
api
.
PodTemplateSpec
)
uint32
{
podTemplateSpecHasher
:=
adler32
.
New
()
util
.
DeepHashObject
(
podTemplateSpecHasher
,
template
)
return
podTemplateSpecHasher
.
Sum32
()
}
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