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
f756e43e
Commit
f756e43e
authored
Sep 08, 2016
by
deads2k
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
convert rolling updater to generated client
parent
11c4de45
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
132 additions
and
94 deletions
+132
-94
conditions.go
pkg/client/unversioned/conditions.go
+14
-11
rollingupdate.go
pkg/kubectl/cmd/rollingupdate.go
+14
-12
clientcache.go
pkg/kubectl/cmd/util/clientcache.go
+37
-0
factory.go
pkg/kubectl/cmd/util/factory.go
+5
-10
rolling_updater.go
pkg/kubectl/rolling_updater.go
+0
-0
scale.go
pkg/kubectl/scale.go
+12
-7
stop.go
pkg/kubectl/stop.go
+50
-54
No files found.
pkg/client/unversioned/conditions.go
View file @
f756e43e
...
...
@@ -25,20 +25,24 @@ import (
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
appsclient
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/unversioned"
batchclient
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/unversioned"
coreclient
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned"
extensionsclient
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned"
"k8s.io/kubernetes/pkg/util/wait"
"k8s.io/kubernetes/pkg/watch"
)
// ControllerHasDesiredReplicas returns a condition that will be true if and only if
// the desired replica count for a controller's ReplicaSelector equals the Replicas count.
func
ControllerHasDesiredReplicas
(
c
Interface
,
controller
*
api
.
ReplicationController
)
wait
.
ConditionFunc
{
func
ControllerHasDesiredReplicas
(
rcClient
coreclient
.
ReplicationControllersGetter
,
controller
*
api
.
ReplicationController
)
wait
.
ConditionFunc
{
// If we're given a controller where the status lags the spec, it either means that the controller is stale,
// or that the rc manager hasn't noticed the update yet. Polling status.Replicas is not safe in the latter case.
desiredGeneration
:=
controller
.
Generation
return
func
()
(
bool
,
error
)
{
ctrl
,
err
:=
c
.
ReplicationControllers
(
controller
.
Namespace
)
.
Get
(
controller
.
Name
)
ctrl
,
err
:=
rcClient
.
ReplicationControllers
(
controller
.
Namespace
)
.
Get
(
controller
.
Name
)
if
err
!=
nil
{
return
false
,
err
}
...
...
@@ -52,7 +56,7 @@ func ControllerHasDesiredReplicas(c Interface, controller *api.ReplicationContro
// ReplicaSetHasDesiredReplicas returns a condition that will be true if and only if
// the desired replica count for a ReplicaSet's ReplicaSelector equals the Replicas count.
func
ReplicaSetHasDesiredReplicas
(
c
ExtensionsInterface
,
replicaSet
*
extensions
.
ReplicaSet
)
wait
.
ConditionFunc
{
func
ReplicaSetHasDesiredReplicas
(
rsClient
extensionsclient
.
ReplicaSetsGetter
,
replicaSet
*
extensions
.
ReplicaSet
)
wait
.
ConditionFunc
{
// If we're given a ReplicaSet where the status lags the spec, it either means that the
// ReplicaSet is stale, or that the ReplicaSet manager hasn't noticed the update yet.
...
...
@@ -60,7 +64,7 @@ func ReplicaSetHasDesiredReplicas(c ExtensionsInterface, replicaSet *extensions.
desiredGeneration
:=
replicaSet
.
Generation
return
func
()
(
bool
,
error
)
{
rs
,
err
:=
c
.
ReplicaSets
(
replicaSet
.
Namespace
)
.
Get
(
replicaSet
.
Name
)
rs
,
err
:=
rsClient
.
ReplicaSets
(
replicaSet
.
Namespace
)
.
Get
(
replicaSet
.
Name
)
if
err
!=
nil
{
return
false
,
err
}
...
...
@@ -73,10 +77,10 @@ func ReplicaSetHasDesiredReplicas(c ExtensionsInterface, replicaSet *extensions.
}
}
func
PetSetHasDesiredPets
(
c
AppsInterface
,
petset
*
apps
.
PetSet
)
wait
.
ConditionFunc
{
func
PetSetHasDesiredPets
(
psClient
appsclient
.
PetSetsGetter
,
petset
*
apps
.
PetSet
)
wait
.
ConditionFunc
{
// TODO: Differentiate between 0 pets and a really quick scale down using generation.
return
func
()
(
bool
,
error
)
{
ps
,
err
:=
c
.
PetSets
(
petset
.
Namespace
)
.
Get
(
petset
.
Name
)
ps
,
err
:=
psClient
.
PetSets
(
petset
.
Namespace
)
.
Get
(
petset
.
Name
)
if
err
!=
nil
{
return
false
,
err
}
...
...
@@ -86,10 +90,9 @@ func PetSetHasDesiredPets(c AppsInterface, petset *apps.PetSet) wait.ConditionFu
// JobHasDesiredParallelism returns a condition that will be true if the desired parallelism count
// for a job equals the current active counts or is less by an appropriate successful/unsuccessful count.
func
JobHasDesiredParallelism
(
c
BatchInterface
,
job
*
batch
.
Job
)
wait
.
ConditionFunc
{
func
JobHasDesiredParallelism
(
jobClient
batchclient
.
JobsGetter
,
job
*
batch
.
Job
)
wait
.
ConditionFunc
{
return
func
()
(
bool
,
error
)
{
job
,
err
:=
c
.
Jobs
(
job
.
Namespace
)
.
Get
(
job
.
Name
)
job
,
err
:=
jobClient
.
Jobs
(
job
.
Namespace
)
.
Get
(
job
.
Name
)
if
err
!=
nil
{
return
false
,
err
}
...
...
@@ -112,7 +115,7 @@ func JobHasDesiredParallelism(c BatchInterface, job *batch.Job) wait.ConditionFu
// DeploymentHasDesiredReplicas returns a condition that will be true if and only if
// the desired replica count for a deployment equals its updated replicas count.
// (non-terminated pods that have the desired template spec).
func
DeploymentHasDesiredReplicas
(
c
ExtensionsInterface
,
deployment
*
extensions
.
Deployment
)
wait
.
ConditionFunc
{
func
DeploymentHasDesiredReplicas
(
dClient
extensionsclient
.
DeploymentsGetter
,
deployment
*
extensions
.
Deployment
)
wait
.
ConditionFunc
{
// If we're given a deployment where the status lags the spec, it either
// means that the deployment is stale, or that the deployment manager hasn't
// noticed the update yet. Polling status.Replicas is not safe in the latter
...
...
@@ -120,7 +123,7 @@ func DeploymentHasDesiredReplicas(c ExtensionsInterface, deployment *extensions.
desiredGeneration
:=
deployment
.
Generation
return
func
()
(
bool
,
error
)
{
deployment
,
err
:=
c
.
Deployments
(
deployment
.
Namespace
)
.
Get
(
deployment
.
Name
)
deployment
,
err
:=
dClient
.
Deployments
(
deployment
.
Namespace
)
.
Get
(
deployment
.
Name
)
if
err
!=
nil
{
return
false
,
err
}
...
...
pkg/kubectl/cmd/rollingupdate.go
View file @
f756e43e
...
...
@@ -27,6 +27,7 @@ import (
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
...
...
@@ -177,24 +178,25 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
return
err
}
client
,
err
:=
f
.
Clien
t
()
client
set
,
err
:=
f
.
ClientSe
t
()
if
err
!=
nil
{
return
err
}
coreClient
:=
clientset
.
Core
()
var
newRc
*
api
.
ReplicationController
// fetch rc
oldRc
,
err
:=
client
.
ReplicationControllers
(
cmdNamespace
)
.
Get
(
oldName
)
oldRc
,
err
:=
c
oreC
lient
.
ReplicationControllers
(
cmdNamespace
)
.
Get
(
oldName
)
if
err
!=
nil
{
if
!
errors
.
IsNotFound
(
err
)
||
len
(
image
)
==
0
||
len
(
args
)
>
1
{
return
err
}
// We're in the middle of a rename, look for an RC with a source annotation of oldName
newRc
,
err
:=
kubectl
.
FindSourceController
(
client
,
cmdNamespace
,
oldName
)
newRc
,
err
:=
kubectl
.
FindSourceController
(
c
oreC
lient
,
cmdNamespace
,
oldName
)
if
err
!=
nil
{
return
err
}
return
kubectl
.
Rename
(
client
,
newRc
,
oldName
)
return
kubectl
.
Rename
(
c
oreC
lient
,
newRc
,
oldName
)
}
var
keepOldName
bool
...
...
@@ -248,10 +250,10 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
// than the old rc. This selector is the hash of the rc, with a suffix to provide uniqueness for
// same-image updates.
if
len
(
image
)
!=
0
{
codec
:=
api
.
Codecs
.
LegacyCodec
(
client
.
APIVersion
())
codec
:=
api
.
Codecs
.
LegacyCodec
(
client
set
.
CoreClient
.
APIVersion
())
keepOldName
=
len
(
args
)
==
1
newName
:=
findNewName
(
args
,
oldRc
)
if
newRc
,
err
=
kubectl
.
LoadExistingNextReplicationController
(
client
,
cmdNamespace
,
newName
);
err
!=
nil
{
if
newRc
,
err
=
kubectl
.
LoadExistingNextReplicationController
(
c
oreC
lient
,
cmdNamespace
,
newName
);
err
!=
nil
{
return
err
}
if
newRc
!=
nil
{
...
...
@@ -274,7 +276,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
}
config
.
PullPolicy
=
api
.
PullPolicy
(
pullPolicy
)
}
newRc
,
err
=
kubectl
.
CreateNewControllerFromCurrentController
(
client
,
codec
,
config
)
newRc
,
err
=
kubectl
.
CreateNewControllerFromCurrentController
(
c
oreC
lient
,
codec
,
config
)
if
err
!=
nil
{
return
err
}
...
...
@@ -287,7 +289,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
}
// If new image is same as old, the hash may not be distinct, so add a suffix.
oldHash
+=
"-orig"
oldRc
,
err
=
kubectl
.
UpdateExistingReplicationController
(
client
,
oldRc
,
cmdNamespace
,
newRc
.
Name
,
deploymentKey
,
oldHash
,
out
)
oldRc
,
err
=
kubectl
.
UpdateExistingReplicationController
(
c
oreClient
,
coreC
lient
,
oldRc
,
cmdNamespace
,
newRc
.
Name
,
deploymentKey
,
oldHash
,
out
)
if
err
!=
nil
{
return
err
}
...
...
@@ -296,7 +298,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
if
rollback
{
keepOldName
=
len
(
args
)
==
1
newName
:=
findNewName
(
args
,
oldRc
)
if
newRc
,
err
=
kubectl
.
LoadExistingNextReplicationController
(
client
,
cmdNamespace
,
newName
);
err
!=
nil
{
if
newRc
,
err
=
kubectl
.
LoadExistingNextReplicationController
(
c
oreC
lient
,
cmdNamespace
,
newName
);
err
!=
nil
{
return
err
}
...
...
@@ -310,7 +312,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
filename
,
oldName
)
}
updater
:=
kubectl
.
NewRollingUpdater
(
newRc
.
Namespace
,
client
)
updater
:=
kubectl
.
NewRollingUpdater
(
newRc
.
Namespace
,
c
oreClient
,
coreC
lient
)
// To successfully pull off a rolling update the new and old rc have to differ
// by at least one selector. Every new pod should have the selector and every
...
...
@@ -367,7 +369,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
if
err
!=
nil
{
return
err
}
client
.
ReplicationControllers
(
config
.
NewRc
.
Namespace
)
.
Update
(
config
.
NewRc
)
c
oreC
lient
.
ReplicationControllers
(
config
.
NewRc
.
Namespace
)
.
Update
(
config
.
NewRc
)
}
err
=
updater
.
Update
(
config
)
if
err
!=
nil
{
...
...
@@ -380,7 +382,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
}
else
{
message
=
fmt
.
Sprintf
(
"rolling updated to %q"
,
newRc
.
Name
)
}
newRc
,
err
=
client
.
ReplicationControllers
(
cmdNamespace
)
.
Get
(
newRc
.
Name
)
newRc
,
err
=
c
oreC
lient
.
ReplicationControllers
(
cmdNamespace
)
.
Get
(
newRc
.
Name
)
if
err
!=
nil
{
return
err
}
...
...
pkg/kubectl/cmd/util/clientcache.go
View file @
f756e43e
...
...
@@ -20,6 +20,7 @@ import (
fed_clientset
"k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/restclient"
client
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
...
...
@@ -28,6 +29,7 @@ import (
func
NewClientCache
(
loader
clientcmd
.
ClientConfig
)
*
ClientCache
{
return
&
ClientCache
{
clients
:
make
(
map
[
unversioned
.
GroupVersion
]
*
client
.
Client
),
clientsets
:
make
(
map
[
unversioned
.
GroupVersion
]
*
internalclientset
.
Clientset
),
configs
:
make
(
map
[
unversioned
.
GroupVersion
]
*
restclient
.
Config
),
fedClientSets
:
make
(
map
[
unversioned
.
GroupVersion
]
fed_clientset
.
Interface
),
loader
:
loader
,
...
...
@@ -39,6 +41,7 @@ func NewClientCache(loader clientcmd.ClientConfig) *ClientCache {
type
ClientCache
struct
{
loader
clientcmd
.
ClientConfig
clients
map
[
unversioned
.
GroupVersion
]
*
client
.
Client
clientsets
map
[
unversioned
.
GroupVersion
]
*
internalclientset
.
Clientset
fedClientSets
map
[
unversioned
.
GroupVersion
]
fed_clientset
.
Interface
configs
map
[
unversioned
.
GroupVersion
]
*
restclient
.
Config
defaultConfig
*
restclient
.
Config
...
...
@@ -95,6 +98,40 @@ func (c *ClientCache) ClientConfigForVersion(version *unversioned.GroupVersion)
return
&
config
,
nil
}
// ClientSetForVersion initializes or reuses a clientset for the specified version, or returns an
// error if that is not possible
func
(
c
*
ClientCache
)
ClientSetForVersion
(
version
*
unversioned
.
GroupVersion
)
(
*
internalclientset
.
Clientset
,
error
)
{
if
version
!=
nil
{
if
clientset
,
ok
:=
c
.
clientsets
[
*
version
];
ok
{
return
clientset
,
nil
}
}
config
,
err
:=
c
.
ClientConfigForVersion
(
version
)
if
err
!=
nil
{
return
nil
,
err
}
clientset
,
err
:=
internalclientset
.
NewForConfig
(
config
)
if
err
!=
nil
{
return
nil
,
err
}
c
.
clientsets
[
*
config
.
GroupVersion
]
=
clientset
// `version` does not necessarily equal `config.Version`. However, we know that if we call this method again with
// `version`, we should get a client based on the same config we just found. There's no guarantee that a client
// is copiable, so create a new client and save it in the cache.
if
version
!=
nil
{
configCopy
:=
*
config
clientset
,
err
:=
internalclientset
.
NewForConfig
(
&
configCopy
)
if
err
!=
nil
{
return
nil
,
err
}
c
.
clientsets
[
*
version
]
=
clientset
}
return
clientset
,
nil
}
// ClientForVersion initializes or reuses a client for the specified version, or returns an
// error if that is not possible
func
(
c
*
ClientCache
)
ClientForVersion
(
version
*
unversioned
.
GroupVersion
)
(
*
client
.
Client
,
error
)
{
...
...
pkg/kubectl/cmd/util/factory.go
View file @
f756e43e
...
...
@@ -431,12 +431,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
return
restclient
.
RESTClientFor
(
clientConfig
)
},
ClientSet
:
func
()
(
*
internalclientset
.
Clientset
,
error
)
{
cfg
,
err
:=
clients
.
ClientConfigForVersion
(
nil
)
if
err
!=
nil
{
return
nil
,
err
}
return
internalclientset
.
NewForConfig
(
cfg
)
return
clients
.
ClientSetForVersion
(
nil
)
},
ClientConfig
:
func
()
(
*
restclient
.
Config
,
error
)
{
return
clients
.
ClientConfigForVersion
(
nil
)
...
...
@@ -706,19 +701,19 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
},
Scaler
:
func
(
mapping
*
meta
.
RESTMapping
)
(
kubectl
.
Scaler
,
error
)
{
mappingVersion
:=
mapping
.
GroupVersionKind
.
GroupVersion
()
client
,
err
:=
clients
.
Clien
tForVersion
(
&
mappingVersion
)
client
set
,
err
:=
clients
.
ClientSe
tForVersion
(
&
mappingVersion
)
if
err
!=
nil
{
return
nil
,
err
}
return
kubectl
.
ScalerFor
(
mapping
.
GroupVersionKind
.
GroupKind
(),
client
)
return
kubectl
.
ScalerFor
(
mapping
.
GroupVersionKind
.
GroupKind
(),
client
set
)
},
Reaper
:
func
(
mapping
*
meta
.
RESTMapping
)
(
kubectl
.
Reaper
,
error
)
{
mappingVersion
:=
mapping
.
GroupVersionKind
.
GroupVersion
()
client
,
err
:=
clients
.
Clien
tForVersion
(
&
mappingVersion
)
client
set
,
err
:=
clients
.
ClientSe
tForVersion
(
&
mappingVersion
)
if
err
!=
nil
{
return
nil
,
err
}
return
kubectl
.
ReaperFor
(
mapping
.
GroupVersionKind
.
GroupKind
(),
client
)
return
kubectl
.
ReaperFor
(
mapping
.
GroupVersionKind
.
GroupKind
(),
client
set
)
},
HistoryViewer
:
func
(
mapping
*
meta
.
RESTMapping
)
(
kubectl
.
HistoryViewer
,
error
)
{
mappingVersion
:=
mapping
.
GroupVersionKind
.
GroupVersion
()
...
...
pkg/kubectl/rolling_updater.go
View file @
f756e43e
This diff is collapsed.
Click to expand it.
pkg/kubectl/scale.go
View file @
f756e43e
...
...
@@ -27,6 +27,11 @@ import (
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
appsclient
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/apps/unversioned"
batchclient
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/batch/unversioned"
coreclient
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned"
extensionsclient
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned"
client
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/util/wait"
...
...
@@ -44,10 +49,10 @@ type Scaler interface {
ScaleSimple
(
namespace
,
name
string
,
preconditions
*
ScalePrecondition
,
newSize
uint
)
(
updatedResourceVersion
string
,
err
error
)
}
func
ScalerFor
(
kind
unversioned
.
GroupKind
,
c
client
.
Interface
)
(
Scaler
,
error
)
{
func
ScalerFor
(
kind
unversioned
.
GroupKind
,
c
*
internalclientset
.
Clientset
)
(
Scaler
,
error
)
{
switch
kind
{
case
api
.
Kind
(
"ReplicationController"
)
:
return
&
ReplicationControllerScaler
{
c
},
nil
return
&
ReplicationControllerScaler
{
c
.
Core
()
},
nil
case
extensions
.
Kind
(
"ReplicaSet"
)
:
return
&
ReplicaSetScaler
{
c
.
Extensions
()},
nil
case
extensions
.
Kind
(
"Job"
),
batch
.
Kind
(
"Job"
)
:
...
...
@@ -155,7 +160,7 @@ func (precondition *ScalePrecondition) ValidateReplicationController(controller
}
type
ReplicationControllerScaler
struct
{
c
c
lient
.
Interface
c
c
oreclient
.
ReplicationControllersGetter
}
// ScaleSimple does a simple one-shot attempt at scaling. It returns the
...
...
@@ -253,7 +258,7 @@ func (precondition *ScalePrecondition) ValidateReplicaSet(replicaSet *extensions
}
type
ReplicaSetScaler
struct
{
c
client
.
ExtensionsInterface
c
extensionsclient
.
ReplicaSetsGetter
}
// ScaleSimple does a simple one-shot attempt at scaling. It returns the
...
...
@@ -324,7 +329,7 @@ func (precondition *ScalePrecondition) ValidateJob(job *batch.Job) error {
}
type
PetSetScaler
struct
{
c
client
.
AppsInterface
c
appsclient
.
PetSetsGetter
}
// ScaleSimple does a simple one-shot attempt at scaling. It returns the
...
...
@@ -377,7 +382,7 @@ func (scaler *PetSetScaler) Scale(namespace, name string, newSize uint, precondi
}
type
JobScaler
struct
{
c
client
.
BatchInterface
c
batchclient
.
JobsGetter
}
// ScaleSimple is responsible for updating job's parallelism. It returns the
...
...
@@ -445,7 +450,7 @@ func (precondition *ScalePrecondition) ValidateDeployment(deployment *extensions
}
type
DeploymentScaler
struct
{
c
client
.
ExtensionsInterface
c
extensionsclient
.
DeploymentsGetter
}
// ScaleSimple is responsible for updating a deployment's desired replicas
...
...
pkg/kubectl/stop.go
View file @
f756e43e
This diff is collapsed.
Click to expand it.
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