Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
vnf-onboarding
OSM Packages
Commits
0252a426
Commit
0252a426
authored
Jun 20, 2022
by
sergio-tarazona
Browse files
new oai-helm procedure created
parent
537efb33
Pipeline
#6135
passed with stage
in 2 minutes and 20 seconds
Changes
84
Pipelines
2
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
464 additions
and
0 deletions
+464
-0
oai-helm/vyos_pnf/helm-charts/eechart/.helmignore
oai-helm/vyos_pnf/helm-charts/eechart/.helmignore
+22
-0
oai-helm/vyos_pnf/helm-charts/eechart/Chart.yaml
oai-helm/vyos_pnf/helm-charts/eechart/Chart.yaml
+5
-0
oai-helm/vyos_pnf/helm-charts/eechart/charts.sample/mysql-8.8.26.tgz
...os_pnf/helm-charts/eechart/charts.sample/mysql-8.8.26.tgz
+0
-0
oai-helm/vyos_pnf/helm-charts/eechart/charts/.gitkeep
oai-helm/vyos_pnf/helm-charts/eechart/charts/.gitkeep
+0
-0
oai-helm/vyos_pnf/helm-charts/eechart/source/configure-remote.yaml
...vyos_pnf/helm-charts/eechart/source/configure-remote.yaml
+11
-0
oai-helm/vyos_pnf/helm-charts/eechart/source/install.sh
oai-helm/vyos_pnf/helm-charts/eechart/source/install.sh
+36
-0
oai-helm/vyos_pnf/helm-charts/eechart/source/install_nginx.sh
...helm/vyos_pnf/helm-charts/eechart/source/install_nginx.sh
+9
-0
oai-helm/vyos_pnf/helm-charts/eechart/source/mylib.py
oai-helm/vyos_pnf/helm-charts/eechart/source/mylib.py
+80
-0
oai-helm/vyos_pnf/helm-charts/eechart/source/playbook.yaml
oai-helm/vyos_pnf/helm-charts/eechart/source/playbook.yaml
+14
-0
oai-helm/vyos_pnf/helm-charts/eechart/source/run_ssh.sh
oai-helm/vyos_pnf/helm-charts/eechart/source/run_ssh.sh
+43
-0
oai-helm/vyos_pnf/helm-charts/eechart/source/vnf_ee.py
oai-helm/vyos_pnf/helm-charts/eechart/source/vnf_ee.py
+74
-0
oai-helm/vyos_pnf/helm-charts/eechart/templates/NOTES.txt
oai-helm/vyos_pnf/helm-charts/eechart/templates/NOTES.txt
+21
-0
oai-helm/vyos_pnf/helm-charts/eechart/templates/_helpers.tpl
oai-helm/vyos_pnf/helm-charts/eechart/templates/_helpers.tpl
+56
-0
oai-helm/vyos_pnf/helm-charts/eechart/templates/configmap.yaml
...elm/vyos_pnf/helm-charts/eechart/templates/configmap.yaml
+6
-0
oai-helm/vyos_pnf/helm-charts/eechart/templates/generator.yaml
...elm/vyos_pnf/helm-charts/eechart/templates/generator.yaml
+7
-0
oai-helm/vyos_pnf/helm-charts/eechart/templates/ingress.yaml
oai-helm/vyos_pnf/helm-charts/eechart/templates/ingress.yaml
+41
-0
oai-helm/vyos_pnf/helm-charts/eechart/templates/mibs.yaml
oai-helm/vyos_pnf/helm-charts/eechart/templates/mibs.yaml
+6
-0
oai-helm/vyos_pnf/helm-charts/eechart/templates/secret.yaml
oai-helm/vyos_pnf/helm-charts/eechart/templates/secret.yaml
+9
-0
oai-helm/vyos_pnf/helm-charts/eechart/templates/service.yaml
oai-helm/vyos_pnf/helm-charts/eechart/templates/service.yaml
+16
-0
oai-helm/vyos_pnf/helm-charts/eechart/templates/serviceaccount.yaml
...yos_pnf/helm-charts/eechart/templates/serviceaccount.yaml
+8
-0
No files found.
oai-helm/vyos_pnf/helm-charts/eechart/.helmignore
0 → 100644
View file @
0252a426
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
oai-helm/vyos_pnf/helm-charts/eechart/Chart.yaml
0 → 100644
View file @
0252a426
apiVersion
:
v1
appVersion
:
"
1.0"
description
:
OSM EE helm chart
name
:
eechart
version
:
0.1.0
oai-helm/vyos_pnf/helm-charts/eechart/charts.sample/mysql-8.8.26.tgz
0 → 100644
View file @
0252a426
File added
oai-helm/vyos_pnf/helm-charts/eechart/charts/.gitkeep
0 → 100644
View file @
0252a426
oai-helm/vyos_pnf/helm-charts/eechart/source/configure-remote.yaml
0 → 100644
View file @
0252a426
-
hosts
:
all
connection
:
local
tasks
:
-
name
:
add static routes
vyos_config
:
lines
:
-
set protocols static route 12.1.1.0/24 next-hop 192.168.18.179
-
set protocols static route 192.168.68.128/26 next-hop 192.168.1.4
-
set protocols static route 192.168.69.128/26 next-hop 192.168.1.5
-
set protocols static route 192.168.70.128/26 next-hop 192.168.1.6
oai-helm/vyos_pnf/helm-charts/eechart/source/install.sh
0 → 100755
View file @
0252a426
#!/bin/bash
##
# 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.
##
echo
"Updating operating system"
apt-get update
# Install ansible libraries
echo
"Installing ansible"
apt-get
install
-y
software-properties-common
apt-add-repository
--yes
--update
ppa:ansible/ansible
apt
install
-y
ansible
# Install library to execute command remotely by ssh
echo
"Installing asynssh"
python3
-m
pip
install
asyncssh
# Install ping system command
apt
install
-y
iputils-ping
# Install HTTP python library
python3
-m
pip
install
requests
# Install MySQL library
python3
-m
pip
install
mysql-connector-python
oai-helm/vyos_pnf/helm-charts/eechart/source/install_nginx.sh
0 → 100755
View file @
0252a426
#!/usr/bin/env bash
set
-eux
sudo
-s
<<
EOF
apt update
apt install -y nginx
systemctl status nginx
EOF
oai-helm/vyos_pnf/helm-charts/eechart/source/mylib.py
0 → 100644
View file @
0252a426
##
# Copyright 2022 Telefonica Investigacion y Desarrollo, S.A.U.
# This file is part of OSM
# 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.
#
# For those usages not covered by the Apache License, Version 2.0 please
# contact with: nfvlabs@tid.es
##
import
logging
import
asyncio
import
asyncssh
import
time
from
mysql.connector
import
connect
,
Error
logger
=
logging
.
getLogger
(
"osm_ee.vnf"
)
async
def
ssh_exec
(
host
:
str
,
user
:
str
,
command
:
str
)
->
(
int
,
str
):
"""
Execute a remote command via SSH.
"""
try
:
async
with
asyncssh
.
connect
(
host
,
username
=
user
,
known_hosts
=
None
)
as
conn
:
logger
.
debug
(
"Executing command '{}'"
.
format
(
command
))
result
=
await
conn
.
run
(
command
)
logger
.
debug
(
"Result: {}"
.
format
(
result
))
return
result
.
exit_status
,
result
.
stderr
except
Exception
as
e
:
logger
.
error
(
"Error: {}"
.
format
(
repr
(
e
)))
return
-
1
,
str
(
e
)
def
mysql_query
(
host
:
str
,
user
:
str
,
password
:
str
,
retries
:
int
,
query
:
str
)
->
(
int
,
str
):
"""
Execute a query to a MySQL database.
"""
text
=
""
logger
.
debug
(
"Host: '{}', user: '{}', password: '{}', query: '{}'"
.
format
(
host
,
user
,
password
,
query
))
for
i
in
range
(
0
,
retries
):
try
:
with
connect
(
host
=
host
,
user
=
user
,
password
=
password
,
)
as
connection
:
with
connection
.
cursor
()
as
cursor
:
cursor
.
execute
(
query
)
for
(
db
)
in
cursor
:
logger
.
debug
(
db
)
text
=
text
+
str
(
db
[
0
])
+
", "
return
0
,
text
[
0
:
len
(
text
)
-
2
]
except
Error
as
e
:
text
=
str
(
e
)
logger
.
debug
(
"Error: {}"
.
format
(
e
))
time
.
sleep
(
3
)
continue
return
-
1
,
text
oai-helm/vyos_pnf/helm-charts/eechart/source/playbook.yaml
0 → 100644
View file @
0252a426
---
-
hosts
:
all
become
:
yes
tasks
:
-
name
:
Wait 120 seconds, but only start checking after 10 seconds
wait_for_connection
:
delay
:
20
timeout
:
120
-
name
:
Install packages
apt
:
name
:
-
"
{{
app
}}"
state
:
latest
cache_valid_time
:
3600
oai-helm/vyos_pnf/helm-charts/eechart/source/run_ssh.sh
0 → 100755
View file @
0252a426
#!/usr/bin/env bash
date
"+%H:%M:%S Starting
$0
..."
IP
=
$1
USERNAME
=
$2
SCRIPT
=
$3
PARAMS
=
$4
DIR
=
$(
dirname
$0
)
date
"+%H:%M:%S Waiting for
$IP
to be ready..."
i
=
5
while
!
ssh
-T
-o
StrictHostKeyChecking
=
no
-o
UserKnownHostsFile
=
/dev/null
-o
ConnectTimeout
=
10
-o
LogLevel
=
ERROR
"
$USERNAME
"
@
"
$IP
"
'exit'
;
do
date
"+%H:%M:%S Error accessing
$IP
, retrying..."
sleep
5
i
=
$((
$i
-
1
))
[
$i
-ge
0
]
||
exit
1
done
date
"+%H:%M:%S SSH server is up, sending script '
${
DIR
}
/
${
SCRIPT
}
'..."
scp
-o
StrictHostKeyChecking
=
no
-o
UserKnownHostsFile
=
/dev/null
${
DIR
}
/
${
SCRIPT
}
"
$USERNAME
"
@
"
$IP
"
:
if
[
$?
-ne
0
]
;
then
date
"+%H:%M:%S scp error"
exit
1
fi
date
"+%H:%M:%S OK. Setting file permissions"
ssh
-T
-o
StrictHostKeyChecking
=
no
-o
UserKnownHostsFile
=
/dev/null
-o
ConnectTimeout
=
10
-o
LogLevel
=
ERROR
"
$USERNAME
"
@
"
$IP
"
"chmod a+x
$SCRIPT
"
if
[
$?
-ne
0
]
;
then
date
"+%H:%M:%S ssh error"
exit
1
fi
COMMAND
=
"./
$SCRIPT
"
[
${#
PARAMS
}
-ge
0
]
||
COMMAND
=
"
${
COMMAND
=
}
$PARAMS
"
date
"+%H:%M:%S Running '
$COMMAND
' on
$IP
..."
ssh
-T
-o
StrictHostKeyChecking
=
no
-o
UserKnownHostsFile
=
/dev/null
-o
ConnectTimeout
=
10
-o
LogLevel
=
ERROR
"
$USERNAME
"
@
"
$IP
"
"
$COMMAND
"
if
[
$?
-ne
0
]
;
then
date
"+%H:%M:%S ssh error"
exit
1
fi
date
"+%H:%M:%S End"
exit
0
oai-helm/vyos_pnf/helm-charts/eechart/source/vnf_ee.py
0 → 100644
View file @
0252a426
##
# 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.
##
import
asyncio
import
asyncssh
import
requests
import
logging
import
os
from
osm_ee.exceptions
import
VnfException
import
osm_ee.util.util_ee
as
util_ee
import
osm_ee.util.util_ansible
as
util_ansible
import
osm_ee.vnf.mylib
as
mylib
class
VnfEE
:
PLAYBOOK_PATH
=
"/app/EE/osm_ee/vnf"
SSH_SCRIPT
=
"/app/EE/osm_ee/vnf/run_ssh.sh"
def
__init__
(
self
,
config_params
):
self
.
logger
=
logging
.
getLogger
(
'osm_ee.vnf'
)
self
.
config_params
=
config_params
# config method saves SSH access parameters (host and username) for future use by other methods.
# It is mandatory in any case.
async
def
config
(
self
,
id
,
params
):
self
.
logger
.
debug
(
"Execute action config, params: {}"
.
format
(
params
))
# Config action is special, params are merged with previous config calls
self
.
config_params
.
update
(
params
)
required_params
=
[
"ssh-hostname"
]
self
.
_check_required_params
(
self
.
config_params
,
required_params
)
yield
"OK"
,
"Configured"
# This method implements the "ansible_playbook" primitive. Uncomment and modify it if a primitive requires
# executing an Ansible Playbook. It needs "playbook-name" parameter (playbook file, must be in "source" directory).
async
def
ansible_playbook
(
self
,
id
,
params
):
self
.
logger
.
debug
(
"Execute action ansible_playbook, params: '{}'"
.
format
(
params
))
try
:
self
.
_check_required_params
(
params
,
[
"playbook-name"
])
params
[
"ansible_user"
]
=
self
.
config_params
[
"ssh-username"
]
params
[
"ansible_password"
]
=
self
.
config_params
[
"ssh-password"
]
inventory
=
self
.
config_params
[
"ssh-hostname"
]
+
","
playbook
=
self
.
PLAYBOOK_PATH
+
"/"
+
params
[
"playbook-name"
]
os
.
environ
[
"ANSIBLE_HOST_KEY_CHECKING"
]
=
"False"
return_code
,
stdout
,
stderr
=
await
util_ansible
.
execute_playbook
(
playbook
,
inventory
,
params
)
status
=
"OK"
if
return_code
==
0
else
"ERROR"
yield
status
,
stdout
+
stderr
except
Exception
as
e
:
self
.
logger
.
debug
(
"Error executing ansible playbook: {}"
.
format
(
repr
(
e
)))
yield
"ERROR"
,
str
(
e
)
# Static method that verifies whether a parameter exists in the map
@
staticmethod
def
_check_required_params
(
params
,
required_params
):
for
required_param
in
required_params
:
if
required_param
not
in
params
:
raise
VnfException
(
"Missing required param: {}"
.
format
(
required_param
))
oai-helm/vyos_pnf/helm-charts/eechart/templates/NOTES.txt
0 → 100755
View file @
0252a426
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "eechart.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "eechart.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "eechart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "eechart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80
{{- end }}
oai-helm/vyos_pnf/helm-charts/eechart/templates/_helpers.tpl
0 → 100755
View file @
0252a426
{{
/*
vim
:
set
filetype
=
mustache
:
*/
}}
{
{
/*
Expand
the
name
of
the
chart
.
*/
}
}
{{- define "eechart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{
{
/*
Create
a
default
fully
qualified
app
name
.
We
truncate
at
63
chars
because
some
Kubernetes
name
fields
are
limited
to
this
(
by
the
DNS
naming
spec
).
If
release
name
contains
chart
name
it
will
be
used
as
a
full
name
.
*/
}
}
{{- define "eechart.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{
{
/*
Create
chart
name
and
version
as
used
by
the
chart
label
.
*/
}
}
{{- define "eechart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{
{
/*
Common
labels
*/
}
}
{{- define "eechart.labels" -}}
app.kubernetes.io/name: {{ include "eechart.name" . }}
helm.sh/chart: {{ include "eechart.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{
{
/*
Create
the
name
of
the
service
account
to
use
*/
}
}
{{- define "eechart.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "eechart.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}
oai-helm/vyos_pnf/helm-charts/eechart/templates/configmap.yaml
0 → 100755
View file @
0252a426
apiVersion
:
v1
kind
:
ConfigMap
metadata
:
name
:
{{
include "eechart.fullname" .
}}
data
:
{{
(.Files.Glob "source/*").AsConfig | indent 2
}}
oai-helm/vyos_pnf/helm-charts/eechart/templates/generator.yaml
0 → 100644
View file @
0252a426
apiVersion
:
v1
kind
:
ConfigMap
metadata
:
name
:
"
vnf-snmp-generator-{{
.Values.global.osm.vnf_id
|
lower
}}"
data
:
generator.yml
:
|-
{{ .Files.Get "snmp/generator.yml" | nindent 4}}
\ No newline at end of file
oai-helm/vyos_pnf/helm-charts/eechart/templates/ingress.yaml
0 → 100755
View file @
0252a426
{{
- if .Values.ingress.enabled -
}}
{{
- $fullName
:
= include "eechart.fullname" . -
}}
{{
- $svcPort
:
= .Values.service.port -
}}
{{
- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -
}}
apiVersion
:
networking.k8s.io/v1beta1
{{
- else -
}}
apiVersion
:
extensions/v1beta1
{{
- end
}}
kind
:
Ingress
metadata
:
name
:
{{
$fullName
}}
labels
:
{{
include "eechart.labels" . | indent 4
}}
{{
- with .Values.ingress.annotations
}}
annotations
:
{{
- toYaml . | nindent 4
}}
{{
- end
}}
spec
:
{{
- if .Values.ingress.tls
}}
tls
:
{{
- range .Values.ingress.tls
}}
-
hosts
:
{{
- range .hosts
}}
-
{{
. | quote
}}
{{
- end
}}
secretName
:
{{
.secretName
}}
{{
- end
}}
{{
- end
}}
rules
:
{{
- range .Values.ingress.hosts
}}
-
host
:
{{
.host | quote
}}
http
:
paths
:
{{
- range .paths
}}
-
path
:
{{
.
}}
backend
:
serviceName
:
{{
$fullName
}}
servicePort
:
{{
$svcPort
}}
{{
- end
}}
{{
- end
}}
{{
- end
}}
oai-helm/vyos_pnf/helm-charts/eechart/templates/mibs.yaml
0 → 100644
View file @
0252a426
apiVersion
:
v1
kind
:
ConfigMap
metadata
:
name
:
"
vnf-snmp-mibs-{{
.Values.global.osm.vnf_id
|
lower}}"
data
:
{{
(.Files.Glob "snmp/mibs/**").AsConfig | indent 2
}}
\ No newline at end of file
oai-helm/vyos_pnf/helm-charts/eechart/templates/secret.yaml
0 → 100644
View file @
0252a426
apiVersion
:
v1
kind
:
Secret
metadata
:
name
:
{{
include "eechart.fullname" .
}}
type
:
Opaque
data
:
mysql_host
:
{{
.Values.mysql.fullnameOverride | b64enc | quote
}}
mysql_user
:
{{
"
root"
|
b64enc | quote
}}
mysql_password
:
{{
.Values.mysql.auth.rootPassword | b64enc | quote
}}
oai-helm/vyos_pnf/helm-charts/eechart/templates/service.yaml
0 → 100755
View file @
0252a426
apiVersion
:
v1
kind
:
Service
metadata
:
name
:
{{
include "eechart.fullname" .
}}
labels
:
{{
include "eechart.labels" . | indent 4
}}
spec
:
type
:
{{
.Values.service.type
}}
ports
:
-
port
:
{{
.Values.service.port
}}
targetPort
:
grpc
protocol
:
TCP
name
:
grpc
selector
:
app.kubernetes.io/name
:
{{
include "eechart.name" .
}}
app.kubernetes.io/instance
:
{{
.Release.Name
}}
oai-helm/vyos_pnf/helm-charts/eechart/templates/serviceaccount.yaml
0 → 100755
View file @
0252a426
{{
- if .Values.serviceAccount.create -
}}
apiVersion
:
v1
kind
:
ServiceAccount
metadata
:
name
:
{{
template "eechart.serviceAccountName" .
}}
labels
:
{{
include "eechart.labels" . | indent 4
}}
{{
- end -
}}
Prev
1
2
3
4
5
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