Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
nni
Commits
e21a6984
Unverified
Commit
e21a6984
authored
Oct 26, 2020
by
liuzhe-lz
Committed by
GitHub
Oct 26, 2020
Browse files
[v2.0] Refactor code hierarchy (part 2) (#2987)
parent
f98ee672
Changes
231
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
424 additions
and
61 deletions
+424
-61
nni/tools/trial_tool/commands.py
nni/tools/trial_tool/commands.py
+0
-0
nni/tools/trial_tool/constants.py
nni/tools/trial_tool/constants.py
+0
-0
nni/tools/trial_tool/file_channel.py
nni/tools/trial_tool/file_channel.py
+0
-0
nni/tools/trial_tool/gpu.py
nni/tools/trial_tool/gpu.py
+0
-0
nni/tools/trial_tool/hdfsClientUtility.py
nni/tools/trial_tool/hdfsClientUtility.py
+0
-0
nni/tools/trial_tool/log_utils.py
nni/tools/trial_tool/log_utils.py
+0
-0
nni/tools/trial_tool/rest_utils.py
nni/tools/trial_tool/rest_utils.py
+0
-0
nni/tools/trial_tool/trial.py
nni/tools/trial_tool/trial.py
+0
-0
nni/tools/trial_tool/trial_keeper.py
nni/tools/trial_tool/trial_keeper.py
+0
-0
nni/tools/trial_tool/trial_runner.py
nni/tools/trial_tool/trial_runner.py
+0
-0
nni/tools/trial_tool/url_utils.py
nni/tools/trial_tool/url_utils.py
+0
-0
nni/tools/trial_tool/web_channel.py
nni/tools/trial_tool/web_channel.py
+0
-0
nni/trial.py
nni/trial.py
+2
-2
nni/tuner.py
nni/tuner.py
+8
-8
nni/utils.py
nni/utils.py
+2
-2
setup.py
setup.py
+178
-44
setup_ts.py
setup_ts.py
+229
-0
test/.coveragerc
test/.coveragerc
+1
-1
test/config/integration_tests.yml
test/config/integration_tests.yml
+2
-2
test/config/integration_tests_tf2.yml
test/config/integration_tests_tf2.yml
+2
-2
No files found.
nni/
nni_
trial_tool/commands.py
→
nni/
tools/
trial_tool/commands.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/constants.py
→
nni/
tools/
trial_tool/constants.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/file_channel.py
→
nni/
tools/
trial_tool/file_channel.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/gpu.py
→
nni/
tools/
trial_tool/gpu.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/hdfsClientUtility.py
→
nni/
tools/
trial_tool/hdfsClientUtility.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/log_utils.py
→
nni/
tools/
trial_tool/log_utils.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/rest_utils.py
→
nni/
tools/
trial_tool/rest_utils.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/trial.py
→
nni/
tools/
trial_tool/trial.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/trial_keeper.py
→
nni/
tools/
trial_tool/trial_keeper.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/trial_runner.py
→
nni/
tools/
trial_tool/trial_runner.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/url_utils.py
→
nni/
tools/
trial_tool/url_utils.py
View file @
e21a6984
File moved
nni/
nni_
trial_tool/web_channel.py
→
nni/
tools/
trial_tool/web_channel.py
View file @
e21a6984
File moved
nni/trial.py
View file @
e21a6984
...
...
@@ -2,8 +2,8 @@
# Licensed under the MIT license.
from
.utils
import
to_json
from
.env_vars
import
trial_env_vars
from
.
import
platform
from
.runtime
.env_vars
import
trial_env_vars
from
.
runtime
import
platform
__all__
=
[
...
...
nni/tuner.py
View file @
e21a6984
...
...
@@ -57,14 +57,14 @@ class Tuner(Recoverable):
See Also
--------
Builtin tuners:
:class:`~nni.hyperopt_tuner.hyperopt_tuner.HyperoptTuner`
:class:`~nni.evolution_tuner.evolution_tuner.EvolutionTuner`
:class:`~nni.smac_tuner.SMACTuner`
:class:`~nni.gridsearch_tuner.GridSearchTuner`
:class:`~nni.networkmorphism_tuner.networkmorphism_tuner.NetworkMorphismTuner`
:class:`~nni.metis_tuner.mets_tuner.MetisTuner`
:class:`~nni.ppo_tuner.PPOTuner`
:class:`~nni.gp_tuner.gp_tuner.GPTuner`
:class:`~nni.
algorithms.hpo.
hyperopt_tuner.hyperopt_tuner.HyperoptTuner`
:class:`~nni.
algorithms.hpo.
evolution_tuner.evolution_tuner.EvolutionTuner`
:class:`~nni.
algorithms.hpo.
smac_tuner.SMACTuner`
:class:`~nni.
algorithms.hpo.
gridsearch_tuner.GridSearchTuner`
:class:`~nni.
algorithms.hpo.
networkmorphism_tuner.networkmorphism_tuner.NetworkMorphismTuner`
:class:`~nni.
algorithms.hpo.
metis_tuner.mets_tuner.MetisTuner`
:class:`~nni.
algorithms.hpo.
ppo_tuner.PPOTuner`
:class:`~nni.
algorithms.hpo.
gp_tuner.gp_tuner.GPTuner`
"""
def
generate_parameters
(
self
,
parameter_id
,
**
kwargs
):
...
...
nni/utils.py
View file @
e21a6984
...
...
@@ -9,8 +9,8 @@ import json_tricks
from
schema
import
And
from
.
import
parameter_expressions
from
.common
import
init_logger
from
.env_vars
import
dispatcher_env_vars
from
.runtime
.common
import
init_logger
from
.runtime
.env_vars
import
dispatcher_env_vars
to_json
=
functools
.
partial
(
json_tricks
.
dumps
,
allow_nan
=
True
)
...
...
setup.py
View file @
e21a6984
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
"""
Script for installation and distribution.
You can use environment variable `NNI_RELEASE` to set release version.
If release version is not set, default to a development build whose version string will be `999.dev0`.
## Development ##
Build and install for development:
$ python setup.py develop
Uninstall:
$ pip uninstall nni
Remove generated files: (use "--all" to remove toolchain and built wheel)
$ python setup.py clean [--all]
Build TypeScript modules without install:
$ python setup.py build_ts
## Release ##
Build wheel package:
$ NNI_RELEASE=2.0 python setup.py build_ts
$ NNI_RELEASE=2.0 python setup.py bdist_wheel -p manylinux1_x86_64
Where "2.0" is version string and "manylinux1_x86_64" is platform.
The platform may also be "macosx_10_9_x86_64" or "win_amd64".
`build_ts` must be manually invoked before `bdist_wheel`,
or setuptools cannot locate JS files which should be packed into wheel.
"""
from
distutils.cmd
import
Command
from
distutils.command.build
import
build
from
distutils.command.clean
import
clean
import
glob
import
os
from
setuptools
import
setup
import
shutil
import
setuptools
from
setuptools.command.develop
import
develop
import
setup_ts
version
=
'999.0.0-developing'
dependencies
=
[
'astor'
,
'hyperopt==0.1.2'
,
'json_tricks'
,
'netifaces'
,
'numpy'
,
'psutil'
,
'ruamel.yaml'
,
'requests'
,
'responses'
,
'scipy'
,
'schema'
,
'PythonWebHDFS'
,
'colorama'
,
'scikit-learn>=0.23.2'
,
'pkginfo'
,
'websockets'
]
release
=
os
.
environ
.
get
(
'NNI_RELEASE'
)
def
_setup
():
setuptools
.
setup
(
name
=
'nni'
,
version
=
release
or
'999.dev0'
,
description
=
'Neural Network Intelligence project'
,
long_description
=
open
(
'README.md'
,
encoding
=
'utf-8'
).
read
(),
long_description_content_type
=
'text/markdown'
,
url
=
'https://github.com/Microsoft/nni'
,
author
=
'Microsoft NNI Team'
,
author_email
=
'nni@microsoft.com'
,
license
=
'MIT'
,
classifiers
=
[
'License :: OSI Approved :: MIT License'
,
'Operating System :: MacOS :: MacOS X'
,
'Operating System :: Microsoft :: Windows :: Windows 10'
,
'Operating System :: POSIX :: Linux'
,
'Programming Language :: Python :: 3 :: Only'
,
'Topic :: Scientific/Engineering :: Artificial Intelligence'
,
],
packages
=
_find_python_packages
(),
package_data
=
{
'nni'
:
[
'**/requirements.txt'
],
'nni_node'
:
_find_node_files
()
# note: this does not work before building
},
python_requires
=
'>=3.6'
,
install_requires
=
dependencies
,
setup_requires
=
[
'requests'
],
entry_points
=
{
'console_scripts'
:
[
'nnictl = nni.tools.nnictl.nnictl:parse_args'
]
},
cmdclass
=
{
'build'
:
Build
,
'build_ts'
:
BuildTs
,
'clean'
:
Clean
,
'develop'
:
Develop
,
}
)
def
_find_python_packages
():
...
...
@@ -24,45 +138,65 @@ def _find_node_files():
return
sorted
(
files
)
setup
(
name
=
'nni'
,
version
=
version
,
author
=
'Microsoft NNI Team'
,
author_email
=
'nni@microsoft.com'
,
description
=
'Neural Network Intelligence project'
,
long_description
=
open
(
'README.md'
,
encoding
=
'utf-8'
).
read
(),
license
=
'MIT'
,
url
=
'https://github.com/Microsoft/nni'
,
packages
=
_find_python_packages
(),
package_data
=
{
'nni'
:
[
'**/requirements.txt'
],
'nni_node'
:
_find_node_files
()
},
python_requires
=
'>=3.6'
,
install_requires
=
[
'astor'
,
'hyperopt==0.1.2'
,
'json_tricks'
,
'netifaces'
,
'numpy'
,
'psutil'
,
'ruamel.yaml'
,
'requests'
,
'responses'
,
'scipy'
,
'schema'
,
'PythonWebHDFS'
,
'colorama'
,
'scikit-learn>=0.23.2'
,
'pkginfo'
,
'websockets'
],
entry_points
=
{
'console_scripts'
:
[
'nnictl = nni.nni_cmd.nnictl:parse_args'
]
}
)
class
BuildTs
(
Command
):
description
=
'build TypeScript modules'
user_options
=
[]
def
initialize_options
(
self
):
pass
def
finalize_options
(
self
):
pass
def
run
(
self
):
setup_ts
.
build
(
release
)
class
Build
(
build
):
def
run
(
self
):
assert
release
,
'Please set environment variable "NNI_RELEASE=<release_version>"'
assert
os
.
path
.
isfile
(
'nni_node/main.js'
),
'Please run "build_ts" before "build"'
assert
not
os
.
path
.
islink
(
'nni_node/main.js'
),
'This is a development build'
super
().
run
()
class
Develop
(
develop
):
def
finalize_options
(
self
):
self
.
user
=
True
# always use `develop --user`
super
().
finalize_options
()
def
run
(
self
):
setup_ts
.
build
(
release
=
None
)
super
().
run
()
class
Clean
(
clean
):
def
finalize_options
(
self
):
self
.
_all
=
self
.
all
self
.
all
=
True
# always use `clean --all`
super
().
finalize_options
()
def
run
(
self
):
super
().
run
()
setup_ts
.
clean
(
self
.
_all
)
_clean_temp_files
()
shutil
.
rmtree
(
'nni.egg-info'
,
ignore_errors
=
True
)
if
self
.
_all
:
shutil
.
rmtree
(
'dist'
,
ignore_errors
=
True
)
def
_clean_temp_files
():
for
pattern
in
_temp_files
:
for
path
in
glob
.
glob
(
pattern
):
if
os
.
path
.
islink
(
path
)
or
os
.
path
.
isfile
(
path
):
os
.
remove
(
path
)
else
:
shutil
.
rmtree
(
path
)
_temp_files
=
[
# unit test
'test/model_path/'
,
'test/temp.json'
,
'test/ut/sdk/*.pth'
]
_setup
()
setup_ts.py
0 → 100644
View file @
e21a6984
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
"""
Script for building TypeScript modules.
This script is called by `setup.py` and common users should avoid using this directly.
It compiles TypeScript source files in `ts` directory,
and copies (or links) JavaScript output as well as dependencies to `nni_node`.
You can set environment `GLOBAL_TOOLCHAIN=1` to use global node and yarn, if you know what you are doing.
"""
from
io
import
BytesIO
import
json
import
os
from
pathlib
import
Path
import
shutil
import
subprocess
import
sys
import
tarfile
from
zipfile
import
ZipFile
node_version
=
'v10.22.1'
yarn_version
=
'v1.22.10'
def
build
(
release
):
"""
Compile TypeScript modules and copy or symlink to nni_node directory.
`release` is the version number without leading letter "v".
If `release` is None or empty, this is a development build and uses symlinks;
otherwise this is a release build and copies files instead.
"""
if
release
or
not
os
.
environ
.
get
(
'GLOBAL_TOOLCHAIN'
):
download_toolchain
()
compile_ts
()
if
release
:
copy_nni_node
(
release
)
else
:
symlink_nni_node
()
def
clean
(
clean_all
=
False
):
"""
Remove TypeScript-related intermediate files.
Python intermediate files are not touched here.
"""
clear_nni_node
()
for
path
in
generated_directories
:
shutil
.
rmtree
(
path
,
ignore_errors
=
True
)
if
clean_all
:
shutil
.
rmtree
(
'toolchain'
,
ignore_errors
=
True
)
Path
(
'nni_node'
,
node_executable
).
unlink
()
if
sys
.
platform
==
'linux'
or
sys
.
platform
==
'darwin'
:
node_executable
=
'node'
node_spec
=
f
'node-
{
node_version
}
-
{
sys
.
platform
}
-x64'
node_download_url
=
f
'https://nodejs.org/dist/latest-v10.x/
{
node_spec
}
.tar.xz'
node_extractor
=
lambda
data
:
tarfile
.
open
(
fileobj
=
BytesIO
(
data
),
mode
=
'r:xz'
)
node_executable_in_tarball
=
'bin/node'
elif
sys
.
platform
==
'win32'
:
node_executable
=
'node.exe'
node_spec
=
f
'node-
{
node_version
}
-win-x64'
node_download_url
=
f
'https://nodejs.org/dist/latest-v10.x/
{
node_spec
}
.zip'
node_extractor
=
lambda
data
:
ZipFile
(
BytesIO
(
data
))
node_executable_in_tarball
=
'node.exe'
else
:
raise
RuntimeError
(
'Unsupported system'
)
yarn_executable
=
'yarn'
if
sys
.
platform
!=
'win32'
else
'yarn.cmd'
yarn_download_url
=
f
'https://github.com/yarnpkg/yarn/releases/download/
{
yarn_version
}
/yarn-
{
yarn_version
}
.tar.gz'
def
download_toolchain
():
"""
Download and extract node and yarn,
then copy node executable to nni_node directory.
"""
if
Path
(
'nni_node'
,
node_executable
).
is_file
():
return
Path
(
'toolchain'
).
mkdir
(
exist_ok
=
True
)
import
requests
# place it here so setup.py can install it before importing
_print
(
f
'Downloading node.js from
{
node_download_url
}
'
)
resp
=
requests
.
get
(
node_download_url
)
resp
.
raise_for_status
()
_print
(
'Extracting node.js'
)
tarball
=
node_extractor
(
resp
.
content
)
tarball
.
extractall
(
'toolchain'
)
shutil
.
rmtree
(
'toolchain/node'
,
ignore_errors
=
True
)
Path
(
'toolchain'
,
node_spec
).
rename
(
'toolchain/node'
)
_print
(
f
'Downloading yarn from
{
yarn_download_url
}
'
)
resp
=
requests
.
get
(
yarn_download_url
)
resp
.
raise_for_status
()
_print
(
'Extracting yarn'
)
tarball
=
tarfile
.
open
(
fileobj
=
BytesIO
(
resp
.
content
),
mode
=
'r:gz'
)
tarball
.
extractall
(
'toolchain'
)
shutil
.
rmtree
(
'toolchain/yarn'
,
ignore_errors
=
True
)
Path
(
f
'toolchain/yarn-
{
yarn_version
}
'
).
rename
(
'toolchain/yarn'
)
src
=
Path
(
'toolchain/node'
,
node_executable_in_tarball
)
dst
=
Path
(
'nni_node'
,
node_executable
)
shutil
.
copyfile
(
src
,
dst
)
def
compile_ts
():
"""
Use yarn to download dependencies and compile TypeScript code.
"""
_print
(
'Building NNI manager'
)
_yarn
(
'ts/nni_manager'
)
_yarn
(
'ts/nni_manager'
,
'build'
)
# todo: I don't think these should be here
shutil
.
rmtree
(
'ts/nni_manager/dist/config'
,
ignore_errors
=
True
)
shutil
.
copytree
(
'ts/nni_manager/config'
,
'ts/nni_manager/dist/config'
)
_print
(
'Building web UI'
)
_yarn
(
'ts/webui'
)
_yarn
(
'ts/webui'
,
'build'
)
_print
(
'Building NAS UI'
)
_yarn
(
'ts/nasui'
)
_yarn
(
'ts/nasui'
,
'build'
)
def
symlink_nni_node
():
"""
Create symlinks to compiled JS files.
If you manually modify and compile TS source files you don't need to install again.
"""
_print
(
'Creating symlinks'
)
clear_nni_node
()
for
path
in
Path
(
'ts/nni_manager/dist'
).
iterdir
():
_symlink
(
path
,
Path
(
'nni_node'
,
path
.
name
))
_symlink
(
'ts/nni_manager/package.json'
,
'nni_node/package.json'
)
_symlink
(
'ts/nni_manager/node_modules'
,
'nni_node/node_modules'
)
_symlink
(
'ts/webui/build'
,
'nni_node/static'
)
Path
(
'nni_node/nasui'
).
mkdir
(
exist_ok
=
True
)
_symlink
(
'ts/nasui/build'
,
'nni_node/nasui/build'
)
_symlink
(
'ts/nasui/server.js'
,
'nni_node/nasui/server.js'
)
def
copy_nni_node
(
version
):
"""
Copy compiled JS files to nni_node.
This is meant for building release package, so you need to provide version string.
The version will written to `package.json` in nni_node directory,
while `package.json` in ts directory will be left unchanged.
"""
_print
(
'Copying files'
)
clear_nni_node
()
# copytree(..., dirs_exist_ok=True) is not supported by Python 3.6
for
path
in
Path
(
'ts/nni_manager/dist'
).
iterdir
():
if
path
.
is_file
():
shutil
.
copyfile
(
path
,
Path
(
'nni_node'
,
path
.
name
))
else
:
shutil
.
copytree
(
path
,
Path
(
'nni_node'
,
path
.
name
))
package_json
=
json
.
load
(
open
(
'ts/nni_manager/package.json'
))
if
version
.
count
(
'.'
)
==
1
:
# node.js semver requires at least three parts
version
=
version
+
'.0'
package_json
[
'version'
]
=
version
json
.
dump
(
package_json
,
open
(
'nni_node/package.json'
,
'w'
),
indent
=
2
)
_yarn
(
'ts/nni_manager'
,
'--prod'
,
'--cwd'
,
str
(
Path
(
'nni_node'
).
resolve
()))
shutil
.
copytree
(
'ts/webui/build'
,
'nni_node/static'
)
Path
(
'nni_node/nasui'
).
mkdir
(
exist_ok
=
True
)
shutil
.
copytree
(
'ts/nasui/build'
,
'nni_node/nasui/build'
)
shutil
.
copyfile
(
'ts/nasui/server.js'
,
'nni_node/nasui/server.js'
)
def
clear_nni_node
():
"""
Remove compiled files in nni_node.
Use `clean()` if you what to remove files in ts as well.
"""
for
path
in
Path
(
'nni_node'
).
iterdir
():
if
path
.
name
not
in
(
'__init__.py'
,
'node'
,
'node.exe'
):
if
path
.
is_symlink
()
or
path
.
is_file
():
path
.
unlink
()
else
:
shutil
.
rmtree
(
path
)
_yarn_env
=
dict
(
os
.
environ
)
_yarn_env
[
'PATH'
]
=
str
(
Path
(
'nni_node'
).
resolve
())
+
':'
+
os
.
environ
[
'PATH'
]
_yarn_path
=
Path
(
'toolchain/yarn/bin'
,
yarn_executable
).
resolve
()
def
_yarn
(
path
,
*
args
):
if
os
.
environ
.
get
(
'GLOBAL_TOOLCHAIN'
):
subprocess
.
run
([
'yarn'
,
*
args
],
cwd
=
path
,
check
=
True
)
else
:
subprocess
.
run
([
_yarn_path
,
*
args
],
cwd
=
path
,
check
=
True
,
env
=
_yarn_env
)
def
_symlink
(
target_file
,
link_location
):
target
=
Path
(
target_file
)
link
=
Path
(
link_location
)
relative
=
os
.
path
.
relpath
(
target
,
link
.
parent
)
link
.
symlink_to
(
relative
,
target
.
is_dir
())
def
_print
(
*
args
):
print
(
'
\033
[1;36m# '
,
end
=
''
)
print
(
*
args
,
end
=
''
)
print
(
'
\033
[0m'
)
generated_directories
=
[
'ts/nni_manager/dist'
,
'ts/nni_manager/node_modules'
,
'ts/webui/build'
,
'ts/webui/node_modules'
,
'ts/nasui/build'
,
'ts/nasui/node_modules'
,
]
test/.coveragerc
View file @
e21a6984
...
...
@@ -3,7 +3,7 @@
branch = True
parallel = True
data_file = ${COVERAGE_DATA_FILE}
source = nni, nni
_
cmd, nni
_
trial_tool
source = nni, nni
.tools.
cmd, nni
.tools.
trial_tool
concurrency = multiprocessing
...
...
test/config/integration_tests.yml
View file @
e21a6984
...
...
@@ -147,8 +147,8 @@ testCases:
config
:
maxTrialNum
:
4
trialConcurrency
:
4
launchCommand
:
python3 -c 'from nni.
nnicli
import Experiment; exp = Experiment(); exp.start_experiment("$configFile")'
stopCommand
:
python3 -c 'from nni.
nnicli
import Experiment; exp = Experiment(); exp.connect_experiment("http://localhost:8080/"); exp.stop_experiment()'
launchCommand
:
python3 -c 'from nni.
experiment
import Experiment; exp = Experiment(); exp.start_experiment("$configFile")'
stopCommand
:
python3 -c 'from nni.
experiment
import Experiment; exp = Experiment(); exp.connect_experiment("http://localhost:8080/"); exp.stop_experiment()'
validator
:
class
:
NnicliValidator
platform
:
linux darwin
...
...
test/config/integration_tests_tf2.yml
View file @
e21a6984
...
...
@@ -110,8 +110,8 @@ testCases:
config
:
maxTrialNum
:
4
trialConcurrency
:
4
launchCommand
:
python3 -c 'from nni.
nnicli
import Experiment; exp = Experiment(); exp.start_experiment("$configFile")'
stopCommand
:
python3 -c 'from nni.
nnicli
import Experiment; exp = Experiment(); exp.connect_experiment("http://localhost:8080/"); exp.stop_experiment()'
launchCommand
:
python3 -c 'from nni.
experiment
import Experiment; exp = Experiment(); exp.start_experiment("$configFile")'
stopCommand
:
python3 -c 'from nni.
experiment
import Experiment; exp = Experiment(); exp.connect_experiment("http://localhost:8080/"); exp.stop_experiment()'
validator
:
class
:
NnicliValidator
platform
:
linux darwin
...
...
Prev
1
…
7
8
9
10
11
12
Next
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