Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
slapos
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Xavier Thompson
slapos
Commits
9d8e5003
Commit
9d8e5003
authored
Nov 27, 2024
by
Joanne Hugé
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
simpleran: fix amarisoft version
User is still allowed to input a hidden parameter to change the version
parent
6494727b
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
103 additions
and
82 deletions
+103
-82
software/simpleran/buildout.hash.cfg
software/simpleran/buildout.hash.cfg
+7
-7
software/simpleran/config/mme.jinja2.cfg
software/simpleran/config/mme.jinja2.cfg
+0
-4
software/simpleran/instance-core-network.jinja2.cfg
software/simpleran/instance-core-network.jinja2.cfg
+12
-21
software/simpleran/instance-enb.jinja2.cfg
software/simpleran/instance-enb.jinja2.cfg
+28
-10
software/simpleran/instance-ors.cfg
software/simpleran/instance-ors.cfg
+2
-1
software/simpleran/instance-ue.jinja2.cfg
software/simpleran/instance-ue.jinja2.cfg
+11
-7
software/simpleran/instance.cfg
software/simpleran/instance.cfg
+41
-30
software/simpleran/ru/libinstance.jinja2.cfg
software/simpleran/ru/libinstance.jinja2.cfg
+2
-2
No files found.
software/simpleran/buildout.hash.cfg
View file @
9d8e5003
...
...
@@ -16,11 +16,11 @@
[template]
filename = instance.cfg
md5sum =
bb6cb40fe200d03435c5c5eae27af958
md5sum =
aa7feb77723afc25bb9403179d6a3f0f
[template-ors]
filename = instance-ors.cfg
md5sum =
8c2abee8eb0a538ad8ae1a84e140af69
md5sum =
1bcf1d10fdcc7f77172b93bf2c50241f
[slaplte.jinja2]
_update_hash_filename_ = slaplte.jinja2
...
...
@@ -36,7 +36,7 @@ md5sum = 0a3a1ecde45a898502f1c66cb26b7d91
[ru_libinstance.jinja2.cfg]
_update_hash_filename_ = ru/libinstance.jinja2.cfg
md5sum =
e436824a4325636fcd479b9711a94044
md5sum =
babef867c5744649a7e3e6c99f390663
[ru_sdr_libinstance.jinja2.cfg]
_update_hash_filename_ = ru/sdr/libinstance.jinja2.cfg
...
...
@@ -60,7 +60,7 @@ md5sum = 52da9fe3a569199e35ad89ae1a44c30e
[template-enb]
_update_hash_filename_ = instance-enb.jinja2.cfg
md5sum =
b116b58365600f12129d458750b57c71
md5sum =
825c912044fdb5c48f36d092db4170f3
[template-ors-enb]
_update_hash_filename_ = instance-ors-enb.jinja2.cfg
...
...
@@ -72,11 +72,11 @@ md5sum = f4389a92fb111447e7976e452db78607
[template-core-network]
_update_hash_filename_ = instance-core-network.jinja2.cfg
md5sum =
dab992c02a363e00cdc86f102a7ae489
md5sum =
87a40b4ebd3baf19771e74bb78d85bcb
[template-ue]
_update_hash_filename_ = instance-ue.jinja2.cfg
md5sum =
0c387a13a57f7270595b74e11be8eb36
md5sum =
1686588ab92d7c9a4bd24402edd0c888
[template-obsolete]
_update_hash_filename_ = instance-obsolete.jinja2.cfg
...
...
@@ -108,7 +108,7 @@ md5sum = 9dbd93036c15c87c6de74b88b34062b6
[mme.jinja2.cfg]
filename = config/mme.jinja2.cfg
md5sum =
b86f0e7a0d890771d56aee22838d6487
md5sum =
d1df6c004ca74330699969818aec3252
[dnsmasq-core-network.jinja2.cfg]
filename = config/dnsmasq-core-network.jinja2.cfg
...
...
software/simpleran/config/mme.jinja2.cfg
View file @
9d8e5003
...
...
@@ -68,14 +68,12 @@
fifteen_bearers: false,
{%- if support_ims == 'True' %}
ims_list: [
{
ims_addr: "{{ slap_configuration['configuration.ims_addr'] }}",
bind_addr: "{{ slap_configuration['configuration.ims_bind'] }}",
}
],
{%- endif %}
pdn_list: [
{
...
...
@@ -101,7 +99,6 @@
},
],
},
{%- if support_ims == 'True' %}
{
access_point_name: "ims",
pdn_type: "ipv4v6",
...
...
@@ -126,7 +123,6 @@
},
],
},
{%- endif %}
],
tun_setup_script: "{{ ifup_empty }}",
...
...
software/simpleran/instance-core-network.jinja2.cfg
View file @
9d8e5003
{%- if lte_version|replace("-", "")|int < 20240502 %}
{%- set support_ims = false %}
{%- else %}
{%- set support_ims = true %}
{%- endif %}
{%- set amarisoft_version = slapparameter_dict.setdefault("amarisoft_version", amarisoft['version']) %}
{%- set amarisoft_dir = slapparameter_dict.setdefault("amarisoft_dir", amarisoft['dir']) %}
{%- set amarisoft_path = amarisoft_dir + "/v" + amarisoft_version %}
{%- set dns_slave_instance_list = [] %}
{%- set sim_slave_instance_list = [] %}
...
...
@@ -78,9 +76,7 @@ parts =
mme-service
ims-config
mt-call-config
{%- if support_ims %}
ims-service
{%- endif %}
monitor-base
check-interface-up.py
publish-connection-information
...
...
@@ -156,7 +152,7 @@ inline =
(echo && echo && date "+[%Y/%m/%d %T.%N %Z] Starting IMS software..." && echo) >> ${:ims-log};
tail -c 1M ${:ims-log} > ${:ims-log}.tmp;
mv ${:ims-log}.tmp ${:ims-log};
{{
ims
}}/lteims ${directory:etc}/ims.cfg >> ${:ims-log} 2>> ${:ims-log};
{{
amarisoft_path }}/{{ amarisoft['ims_dir']
}}/lteims ${directory:etc}/ims.cfg >> ${:ims-log} 2>> ${:ims-log};
{% endif %}
### IMS
...
...
@@ -173,7 +169,7 @@ hash-files =
${ims-sh-wrapper:output}
environment =
LD_LIBRARY_PATH={{ openssl_location }}/lib:{{ nghttp2_location }}/lib
AMARISOFT_PATH=
/opt/amarisoft/.amarisoft
AMARISOFT_PATH=
{{ amarisoft_dir }}/{{ amarisoft['license_dir'] }}
[mme-sh-wrapper]
recipe = slapos.recipe.template
...
...
@@ -182,12 +178,12 @@ mme-log = ${directory:log}/mme-output.log
inline =
#!/bin/sh
{% if not slapparameter_dict.get("testing", False) %}
sudo -n
/opt/amarisoft
/init-mme;
sudo -n
{{ amarisoft_dir }}
/init-mme;
rm -f ${directory:var}/lte_ue.db;
(echo && echo && date "+[%Y/%m/%d %T.%N %Z] Starting MME software..." && echo) >> ${:mme-log};
tail -c 1M ${:mme-log} > ${:mme-log}.tmp;
mv ${:mme-log}.tmp ${:mme-log};
{{
mme
}}/ltemme ${directory:etc}/mme.cfg >> ${:mme-log} 2>> ${:mme-log};
{{
amarisoft_path }}/{{ amarisoft['mme_dir']
}}/ltemme ${directory:etc}/mme.cfg >> ${:mme-log} 2>> ${:mme-log};
{% endif %}
### MME
...
...
@@ -205,7 +201,7 @@ hash-files =
${mme-sh-wrapper:output}
environment =
LD_LIBRARY_PATH={{ openssl_location }}/lib:{{ nghttp2_location }}/lib
AMARISOFT_PATH=
/opt/amarisoft/.amarisoft
AMARISOFT_PATH=
{{ amarisoft_dir }}/{{ amarisoft['license_dir'] }}
### EMPTY mme-ifup script
[mme-ifup-empty]
...
...
@@ -269,8 +265,6 @@ context =
<= config-base
url = {{ mme_template }}
output = ${directory:etc}/mme.cfg
extra-context =
raw support_ims {{ support_ims }}
{% if slapparameter_dict.get("local_domain", '') %}
[dnsmasq-config]
...
...
@@ -321,15 +315,12 @@ password = {{ slapparameter_dict['monitor-password'] | string }}
[publish-connection-information]
<= monitor-publish
recipe = slapos.cookbook:publish.serialised
{%- if support_ims %}
ims = Enabled
{%- else %}
ims = Unsupported (Amarisoft version >= 2024-05-02 is required), 5G may not work with your UE
{%- endif %}
core-network-ipv6 = {{ my_ipv6 }}
core-network-ipv4 = {{ lan_ipv4 }}
amarisoft-version = {{ lte_version }}
license-expiration = {{ lte_expiration }}
amarisoft-version = {{ amarisoft_version}}
amarisoft-host-id = {{ amarisoft['lteenb_host_id'] }}
amarisoft-available-versions = {{ amarisoft['version_installed'] }}
license-expiration = {{ amarisoft['ltemme_expiration'] }}
monitor-gadget-url = ${:monitor-base-url}/gadget/software.cfg.html
sim-list = {{ imsi_list | join(', ') }}
...
...
software/simpleran/instance-enb.jinja2.cfg
View file @
9d8e5003
# instance-enb implements eNB/gNB service.
{%- set amarisoft_version = slapparameter_dict.setdefault("amarisoft_version", amarisoft['version']) %}
{%- set amarisoft_dir = slapparameter_dict.setdefault("amarisoft_dir", amarisoft['dir']) %}
{%- set amarisoft_path = amarisoft_dir + "/v" + amarisoft_version %}
{#- defaults for global eNB/gNB parameters.
TODO automatically load enb defaults from JSON schema #}
{%- set enb_defaults = {
...
...
@@ -104,15 +108,14 @@ inline =
#!/bin/sh
{% if not slapparameter_dict.get("testing", False) %}
# Amarisoft init scripts
sudo -n
/opt/amarisoft
/rm-tmp-lte
sudo -n
/opt/amarisoft/init-sdr
sudo -n
/opt/amarisoft
/init-enb
sudo -n
{{ amarisoft_dir }}
/rm-tmp-lte
sudo -n
{{ amarisoft_dir }}/init-sdr {{ amarisoft_path }}
sudo -n
{{ amarisoft_dir }}
/init-enb
# Add useful information to enb-info log
(echo && echo && date "+[%Y/%m/%d %T.%N %Z] Starting eNB software...") >> ${:enb-info-log}
(echo -n "PCB: " ; for o in t b v s ; do sudo -n /opt/amarisoft/get-sdr-info -$o 2> /dev/null ; echo -n " " ; done ; echo) >> ${:enb-info-log}
(AMARISOFT_PATH=/dev/null {{ enb }}/lteenb ${directory:etc}/enb.cfg 2>&1 >/dev/null | sed -n 's/^.*\(Host ID.*\)$/\1/gp') >> ${:enb-info-log}
(echo -n "PCB: " ; for o in t b v s ; do sudo -n {{ amarisoft['sdr_dir'] }}/get-sdr-info -$o 2> /dev/null ; echo -n " " ; done ; echo) >> ${:enb-info-log}
echo "System info: $(uname -a)" >> ${:enb-info-log}
({{
sdr
}}/sdr_util version && echo) >> ${:enb-info-log}
({{
amarisoft_path }}/{{ amarisoft['sdr_dir']
}}/sdr_util version && echo) >> ${:enb-info-log}
# Remove obsolete logs
rm -f ${directory:log}/enb-2024*
rm -f ${directory:log}/gnb*
...
...
@@ -129,7 +132,7 @@ inline =
tail -c 100M ${:enb-info-archive-log} > ${:enb-info-archive-log}.tmp
mv ${:enb-info-archive-log}.tmp ${:enb-info-archive-log}
# Launch lteenb
{{
enb
}}/lteenb ${directory:etc}/enb.cfg >> ${:enb-info-log} 2>> ${:enb-info-log}
{{
amarisoft_path }}/{{ amarisoft['enb_dir']
}}/lteenb ${directory:etc}/enb.cfg >> ${:enb-info-log} 2>> ${:enb-info-log}
{% endif %}
[enb-service]
...
...
@@ -144,7 +147,7 @@ hash-files =
${enb-sh-wrapper:output}
environment =
LD_LIBRARY_PATH={{ openssl_location }}/lib
AMARISOFT_PATH=
/opt/amarisoft/.amarisoft
AMARISOFT_PATH=
{{ amarisoft_dir }}/{{ amarisoft['license_dir'] }}
[xamari-xlog-script]
recipe = slapos.recipe.template
...
...
@@ -170,6 +173,19 @@ wrapper-path = ${directory:service}/${:_buildout_section_name_}
command-line = ${xamari-xlog-script:output}
hash-files = ${:command-line}
[request-parameters]
<= slap-connection
recipe = slapos.cookbook:requestoptional
name = Wendelin Telecom Registration
software-url = {{ slapparameter_dict.wendelin_telecom_software_release_url }}
shared = true
{%- if not slapparameter_dict.xlog_forwarding_enabled or slapparameter_dict.get("xlog_fluentbit_forward_host") %}
state = destroyed
{%- else %}
config-fluentbit-tag = ${xlog-fluentbit-tag:xlog-fluentbit-tag}
return = gateway-host
{%- endif %}
[request-wendelin-telecom-shared]
<= slap-connection
recipe = slapos.cookbook:requestoptional
...
...
@@ -307,8 +323,10 @@ websocket-port = 443
websocket-password = ${websocket-password:passwd}
enb-ipv6 = {{ my_ipv6 }}
enb-ipv4 = {{ lan_ipv4 }}
amarisoft-version = {{ lte_version }}
license-expiration = {{ lte_expiration }}
amarisoft-version = {{ amarisoft['version'] }}
amarisoft-host-id = {{ amarisoft['lteenb_host_id'] }}
amarisoft-available-versions = {{ amarisoft['version_installed'] }}
license-expiration = {{ amarisoft['lteenb_expiration'] }}
monitor-gadget-url = ${:monitor-base-url}/gadget/software.cfg.html
ru-list = {{ dumps(rulib.iru_dict.keys() | sort) }}
cell-list = {{ dumps(rulib.icell_dict.keys() | sort) }}
...
...
software/simpleran/instance-ors.cfg
View file @
9d8e5003
...
...
@@ -48,6 +48,7 @@ import-list +=
[ors-version]
recipe = slapos.recipe.build
configuration = $${slap-configuration:configuration}
sdr-dir = $${sdr:dir}
init =
import subprocess
range_map = {
...
...
@@ -168,7 +169,7 @@ init =
if options['configuration'].get('testing', False):
return {'t': 'TDD', 'b': 'B39', 'v': '4.2', 's': 'B53'}[cmd].encode()
return subprocess.check_output(
["sudo", "-n",
"/opt/amarisoft
/get-sdr-info", "-" + cmd]
["sudo", "-n",
options['sdr-dir'] + "
/get-sdr-info", "-" + cmd]
)
version = get_sdr_info('v').decode()
options['version'] = float(version) if version != 'UNKNOWN' else 0
...
...
software/simpleran/instance-ue.jinja2.cfg
View file @
9d8e5003
# instance-ue implements UEsim service.
#
{%- set amarisoft_version = slapparameter_dict.setdefault("amarisoft_version", amarisoft['version']) %}
{%- set amarisoft_dir = slapparameter_dict.setdefault("amarisoft_dir", amarisoft['dir']) %}
{%- set amarisoft_path = amarisoft_dir + "/v" + amarisoft_version %}
{#- defaults for global UE parameters.
TODO automatically load ue defaults from JSON schema #}
{%- set ue_defaults = {
...
...
@@ -77,9 +81,9 @@ ue-start-date = ${directory:run}/enb-start.date
inline =
#!/bin/sh
{% if not slapparameter_dict.get("testing", False) %}
sudo
/opt/amarisoft
/rm-tmp-lte | true;
sudo -n
/opt/amarisoft/init-sdr
;
sudo -n
/opt/amarisoft
/init-ue;
sudo
{{ amarisoft_dir }}
/rm-tmp-lte | true;
sudo -n
{{ amarisoft_dir }}/init-sdr {{ amarisoft_path }}
;
sudo -n
{{ amarisoft_dir }}
/init-ue;
stat ${:ue-start-date} && mv ${:ue-radio-log} ${directory:log}/ue-$(cat ${:ue-start-date}).log
rm -f $(ls -1t ${directory:log}/ue-2* | tail -n+50)
date +"%Y-%m-%d-%T" > ${:ue-start-date}
...
...
@@ -87,9 +91,9 @@ inline =
tail -c 1M ${:ue-log} > ${:ue-log}.tmp;
mv ${:ue-log}.tmp ${:ue-log};
{%- if ors %}
echo "power_on" | sudo -n {{
ue
}}/lteue ${directory:etc}/ue.cfg >> ${:ue-log} 2>> ${:ue-log};
echo "power_on" | sudo -n {{
amarisoft_path }}/{{ amarisoft['ue_dir']
}}/lteue ${directory:etc}/ue.cfg >> ${:ue-log} 2>> ${:ue-log};
{%- else %}
{{
ue
}}/lteue ${directory:etc}/ue.cfg >> ${:ue-log} 2>> ${:ue-log};
{{
amarisoft_path }}/{{ amarisoft['ue_dir']
}}/lteue ${directory:etc}/ue.cfg >> ${:ue-log} 2>> ${:ue-log};
{%- endif %}
{%- endif %}
...
...
@@ -106,7 +110,7 @@ hash-files =
${lte-ue-sh-wrapper:output}
environment =
LD_LIBRARY_PATH={{ openssl_location }}/lib
AMARISOFT_PATH=
/opt/amarisoft/.amarisoft
AMARISOFT_PATH=
{{ amarisoft_dir }}/{{ amarisoft['license_dir'] }}
[config-base]
recipe = slapos.recipe.template:jinja2
...
...
software/simpleran/instance.cfg
View file @
9d8e5003
...
...
@@ -80,37 +80,56 @@ init =
assert 'xbuildout' not in sys.modules
sys.modules['xbuildout'] = xbuildout
[sdr]
recipe = slapos.recipe.build
dir = /opt/sdr
init =
pass
[amarisoft]
recipe = slapos.recipe.build
fixed_version = 2024-11-21.1732633257
configuration = $${slap-configuration:configuration}
init =
import os, re
# Set Amarisoft directory
options['dir'] = options['configuration'].get('amarisoft_dir', '/opt/amarisoft')
# Get Available Amarisoft versions
options['version_installed'] = ', '.join(filter(lambda x: re.match(r"v[0-9]{4}-[0-9]{2}-[0-9]{2}.[0-9]{10}", x), os.listdir(options['dir'])))
# Set Amarisoft version to use
slapconf_version = options['configuration'].get('amarisoft_version', False)
if slapconf_version and \
slapconf_version in options['version_installed']:
options['version'] = slapconf_version
else:
options['version'] = options['fixed_version']
# Set Binaries and license directories
binary_dir = options['dir'] + "/v" + options['version'].
options['license_dir'] = options['dir'] + '/.amarisoft'
options['sdr_dir'] = options['binary_dir'] + '/trx_sdr'
options['enb_dir'] = options['binary_dir'] + '/enb'
options['mme_dir'] = options['binary_dir'] + '/mme'
options['ims_dir'] = options['binary_dir'] + '/mme'
options['ue_dir'] = options['binary_dir'] + '/ue'
# Get License expiration and host IDs
options.update({'lteenb_expiration': 'Unknown', 'ltemme_expiration': 'Unknown'})
options.update({'lteenb_host_id': 'Unknown', 'ltemme_host_id': 'Unknown'})
try:
lte_version = sorted(filter(lambda x: re.match(r"v[0-9]{4}-[0-9]{2}-[0-9]{2}", x), os.listdir('/opt/amarisoft')))[-1][1:]
except FileNotFoundError:
lte_version = 'LTEVERSION'
path = "/opt/amarisoft/v" + lte_version
options['lte-version'] = lte_version
options['path'] = path
options['sdr'] = path + "/trx_sdr"
options['enb'] = path + "/enb"
options['mme'] = path + "/mme"
options['ims'] = path + "/mme"
options['ue'] = path + "/ue"
import os
lte_expiration = "Unknown"
amarisoft_dir = '/opt/amarisoft/.amarisoft'
try:
for filename in os.listdir(amarisoft_dir):
for filename in os.listdir(options['license_dir']):
if filename.endswith('.key'):
with open(os.path.join(
amarisoft_dir
, filename), 'r') as f:
with open(os.path.join(
options['license_dir']
, filename), 'r') as f:
f.seek(260)
for l in f:
if l.startswith('host_id='):
host_id = l.split('=')[1].strip()
if l.startswith('product_id='):
product_id = l.split('=')[1].strip()
if l.startswith('version='):
lte_expiration = l.split('=')[1].strip()
expiration = l.split('=')[1].strip()
options[product_id + '_expiration'] = expiration
options[product_id + '_host_id'] = host_id
except FileNotFoundError:
pass
options['lte-expiration'] = lte_expiration
[lan-ip]
recipe = slapos.recipe.build
...
...
@@ -151,10 +170,7 @@ extra-context =
raw monitor_template ${monitor2-template:output}
section comp_id comp-id
section slap_configuration slap-configuration
key lte_version amarisoft:lte-version
key lte_expiration amarisoft:lte-expiration
key enb amarisoft:enb
key sdr amarisoft:sdr
section amarisoft amarisoft
raw enb_template ${enb.jinja2.cfg:target}
raw slaplte_template ${slaplte.jinja2:target}
raw drb_lte_template ${drb_lte.jinja2.cfg:target}
...
...
@@ -178,10 +194,7 @@ filename = instance-core-network.cfg
extensions = jinja2.ext.do
extra-context =
raw monitor_template ${monitor2-template:output}
key lte_version amarisoft:lte-version
key lte_expiration amarisoft:lte-expiration
key mme amarisoft:mme
key ims amarisoft:ims
section amarisoft amarisoft
raw mme_template ${mme.jinja2.cfg:target}
raw dnsmasq_template ${dnsmasq-core-network.jinja2.cfg:target}
raw ims_template ${ims.jinja2.cfg:target}
...
...
@@ -202,13 +215,11 @@ filename = instance-ue.cfg
extensions = jinja2.ext.do
extra-context =
section slap_configuration slap-configuration
section amarisoft amarisoft
raw monitor_template ${monitor2-template:output}
key ue amarisoft:ue
key sdr amarisoft:sdr
raw ue_template ${ue.jinja2.cfg:target}
raw slaplte_template ${slaplte.jinja2:target}
raw openssl_location ${openssl:location}
raw ru_amarisoft_stats_template ${ru_amarisoft-stats.jinja2.py:target}
raw ru_amarisoft_rf_info_template ${ru_amarisoft-rf-info.jinja2.py:target}
raw ru_tapsplit ${ru_tapsplit:target}
...
...
software/simpleran/ru/libinstance.jinja2.cfg
View file @
9d8e5003
...
...
@@ -196,7 +196,7 @@ hash-files =
{%- for (i, n) in enumerate(ru.sdr_dev_list) %}
{{ promise('%s-sdr-busy%s' % (ru_ref, '-%d' % (i+1) if i > 0 else '')) }}
promise = check_sdr_busy
config-sdr = {{
sdr
}}
config-sdr = {{
amarisoft['sdr_dir']
}}
config-sdr_dev = {{ n }}
config-dma_chan = 0
{%- endfor %}
...
...
@@ -204,7 +204,7 @@ config-dma_chan = 0
{%- elif ru.ru_link_type == 'cpri' %}
{{ promise('%s-sdr-busy' % ru_ref) }}
promise = check_sdr_busy
config-sdr = {{
sdr
}}
config-sdr = {{
amarisoft['sdr_dir']
}}
config-sdr_dev = {{ ru.cpri_link.sdr_dev }}
config-dma_chan = {{ ru.cpri_link.sfp_port }}
...
...
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