Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
atlas
Project
Overview
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
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
dataplatform
atlas
Commits
4a938d87
Commit
4a938d87
authored
Apr 11, 2018
by
apoorvnaik
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ATLAS-2578: Update metric queries for faster execution
Change-Id: Ic17ce74f8309fa61f9946d7295b1d161374ebe26
parent
85aef3c0
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
70 additions
and
86 deletions
+70
-86
AtlasMetrics.java
...ain/java/org/apache/atlas/model/metrics/AtlasMetrics.java
+21
-21
MetricsService.java
...c/main/java/org/apache/atlas/services/MetricsService.java
+0
-0
AtlasGremlin2QueryProvider.java
...ava/org/apache/atlas/util/AtlasGremlin2QueryProvider.java
+4
-15
AtlasGremlin3QueryProvider.java
...ava/org/apache/atlas/util/AtlasGremlin3QueryProvider.java
+4
-4
AtlasGremlinQueryProvider.java
...java/org/apache/atlas/util/AtlasGremlinQueryProvider.java
+1
-1
MetricsServiceTest.java
...st/java/org/apache/atlas/services/MetricsServiceTest.java
+39
-44
AdminResource.java
...in/java/org/apache/atlas/web/resources/AdminResource.java
+1
-1
No files found.
intg/src/main/java/org/apache/atlas/model/metrics/AtlasMetrics.java
View file @
4a938d87
...
@@ -36,13 +36,13 @@ import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.PUBLIC_
...
@@ -36,13 +36,13 @@ import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.PUBLIC_
@JsonSerialize
(
include
=
JsonSerialize
.
Inclusion
.
NON_NULL
)
@JsonSerialize
(
include
=
JsonSerialize
.
Inclusion
.
NON_NULL
)
@JsonIgnoreProperties
(
ignoreUnknown
=
true
)
@JsonIgnoreProperties
(
ignoreUnknown
=
true
)
public
class
AtlasMetrics
{
public
class
AtlasMetrics
{
private
Map
<
String
,
Map
<
String
,
Number
>>
data
;
private
Map
<
String
,
Map
<
String
,
Object
>>
data
;
public
AtlasMetrics
()
{
public
AtlasMetrics
()
{
setData
(
null
);
setData
(
null
);
}
}
public
AtlasMetrics
(
Map
<
String
,
Map
<
String
,
Number
>>
data
)
{
public
AtlasMetrics
(
Map
<
String
,
Map
<
String
,
Object
>>
data
)
{
setData
(
data
);
setData
(
data
);
}
}
...
@@ -52,41 +52,41 @@ public class AtlasMetrics {
...
@@ -52,41 +52,41 @@ public class AtlasMetrics {
}
}
}
}
public
Map
<
String
,
Map
<
String
,
Number
>>
getData
()
{
public
Map
<
String
,
Map
<
String
,
Object
>>
getData
()
{
return
data
;
return
data
;
}
}
public
void
setData
(
Map
<
String
,
Map
<
String
,
Number
>>
data
)
{
public
void
setData
(
Map
<
String
,
Map
<
String
,
Object
>>
data
)
{
this
.
data
=
data
;
this
.
data
=
data
;
}
}
@JsonIgnore
@JsonIgnore
public
void
add
Data
(
String
groupKey
,
String
key
,
Number
value
)
{
public
void
add
Metric
(
String
groupKey
,
String
key
,
Object
value
)
{
Map
<
String
,
Map
<
String
,
Number
>>
data
=
this
.
data
;
Map
<
String
,
Map
<
String
,
Object
>>
data
=
this
.
data
;
if
(
data
==
null
)
{
if
(
data
==
null
)
{
data
=
new
HashMap
<>();
data
=
new
HashMap
<>();
}
}
Map
<
String
,
Number
>
metricMap
=
data
.
get
(
groupKey
);
Map
<
String
,
Object
>
metricMap
=
data
.
computeIfAbsent
(
groupKey
,
k
->
new
HashMap
<>());
if
(
metricMap
==
null
)
{
metricMap
=
new
HashMap
<>();
data
.
put
(
groupKey
,
metricMap
);
}
metricMap
.
put
(
key
,
value
);
metricMap
.
put
(
key
,
value
);
setData
(
data
);
setData
(
data
);
}
}
@JsonIgnore
@JsonIgnore
public
Number
getMetric
(
String
groupKey
,
String
key
)
{
public
Number
getNumericMetric
(
String
groupKey
,
String
key
)
{
Map
<
String
,
Map
<
String
,
Number
>>
data
=
this
.
data
;
Object
metric
=
getMetric
(
groupKey
,
key
);
if
(
data
==
null
)
{
return
metric
instanceof
Number
?
(
Number
)
metric
:
null
;
return
null
;
}
}
else
{
Map
<
String
,
Number
>
metricMap
=
data
.
get
(
groupKey
);
@JsonIgnore
if
(
metricMap
==
null
||
metricMap
.
isEmpty
())
{
public
Object
getMetric
(
String
groupKey
,
String
key
)
{
return
null
;
Map
<
String
,
Map
<
String
,
Object
>>
data
=
this
.
data
;
}
else
{
Object
ret
=
null
;
return
metricMap
.
get
(
key
);
if
(
data
!=
null
)
{
Map
<
String
,
Object
>
metricMap
=
data
.
get
(
groupKey
);
if
(
metricMap
!=
null
&&
!
metricMap
.
isEmpty
())
{
ret
=
metricMap
.
get
(
key
);
}
}
}
}
return
ret
;
}
}
}
}
repository/src/main/java/org/apache/atlas/services/MetricsService.java
View file @
4a938d87
This diff is collapsed.
Click to expand it.
repository/src/main/java/org/apache/atlas/util/AtlasGremlin2QueryProvider.java
View file @
4a938d87
...
@@ -21,22 +21,11 @@ public class AtlasGremlin2QueryProvider extends AtlasGremlinQueryProvider {
...
@@ -21,22 +21,11 @@ public class AtlasGremlin2QueryProvider extends AtlasGremlinQueryProvider {
@Override
@Override
public
String
getQuery
(
final
AtlasGremlinQuery
gremlinQuery
)
{
public
String
getQuery
(
final
AtlasGremlinQuery
gremlinQuery
)
{
switch
(
gremlinQuery
)
{
switch
(
gremlinQuery
)
{
case
TYPE_COUNT_METRIC:
case
ENTITY_ACTIVE_METRIC:
return
"g.V().has('__type', 'typeSystem').filter({!it.'__type.category'.name().matches('TRAIT')}).count()"
;
return
"g.V().has('__typeName', T.in, [%s]).has('__state', 'ACTIVE').groupCount{it.getProperty('__typeName')}.cap.toList()"
;
case
TYPE_UNUSED_COUNT_METRIC:
return
"g.V('__type', 'typeSystem').filter({ !it.getProperty('__type.category').name().matches('TRAIT') && it.inE().count() == 0}).count()"
;
case
ENTITY_COUNT_METRIC:
return
"g.V().has('__superTypeNames', T.in, ['Referenceable']).count()"
;
case
TAG_COUNT_METRIC:
return
"g.V().has('__type', 'typeSystem').filter({it.getProperty('__type.category').name().matches('TRAIT')}).count()"
;
case
ENTITY_DELETED_METRIC:
case
ENTITY_DELETED_METRIC:
return
"g.V().has('__typeName', T.in, g.V().has('__type', 'typeSystem').filter{it.getProperty('__type.category').name().matches('CLASS')}.'__type.name'.toSet()).has('__status', 'DELETED').count()"
;
return
"g.V().has('__typeName', T.in, [%s]).has('__state', 'DELETED').groupCount{it.getProperty('__typeName')}.cap.toList()"
;
case
ENTITIES_PER_TYPE_METRIC:
return
"g.V().has('__typeName', T.in, g.V().has('__type', 'typeSystem').filter{it.getProperty('__type.category').name() == 'CLASS'}.'__type.name'.toSet()).groupCount{it.getProperty('__typeName')}.cap.toList()"
;
case
TAGGED_ENTITIES_METRIC:
return
"g.V().has('__traitNames', T.in, g.V().has('__type', 'typeSystem').filter{it.getProperty('__type.category').name() == 'TRAIT'}.'__type.name'.toSet()).count()"
;
case
ENTITIES_FOR_TAG_METRIC:
return
"g.V().has('__typeName', T.in, g.V().has('__type', 'typeSystem').filter{it.getProperty('__type.category').name() == 'TRAIT'}.'__type.name'.toSet()).groupCount{it.getProperty('__typeName')}.cap.toList()"
;
case
EXPORT_BY_GUID_FULL:
case
EXPORT_BY_GUID_FULL:
return
"g.V('__guid', startGuid).bothE().bothV().has('__guid').transform{[__guid:it.__guid,isProcess:(it.__superTypeNames != null) ? it.__superTypeNames.contains('Process') : false ]}.dedup().toList()"
;
return
"g.V('__guid', startGuid).bothE().bothV().has('__guid').transform{[__guid:it.__guid,isProcess:(it.__superTypeNames != null) ? it.__superTypeNames.contains('Process') : false ]}.dedup().toList()"
;
case
EXPORT_BY_GUID_CONNECTED_IN_EDGE:
case
EXPORT_BY_GUID_CONNECTED_IN_EDGE:
...
...
repository/src/main/java/org/apache/atlas/util/AtlasGremlin3QueryProvider.java
View file @
4a938d87
...
@@ -23,10 +23,10 @@ public class AtlasGremlin3QueryProvider extends AtlasGremlin2QueryProvider {
...
@@ -23,10 +23,10 @@ public class AtlasGremlin3QueryProvider extends AtlasGremlin2QueryProvider {
// In case any overrides are necessary, a specific switch case can be added here to
// In case any overrides are necessary, a specific switch case can be added here to
// return Gremlin 3 specific query otherwise delegate to super.getQuery
// return Gremlin 3 specific query otherwise delegate to super.getQuery
switch
(
gremlinQuery
)
{
switch
(
gremlinQuery
)
{
case
TYPE_UNUSED_COUNT
_METRIC:
case
ENTITY_ACTIVE
_METRIC:
return
"g.V().has('__type
', 'typeSystem').filter({ !it.getProperty('__type.category').name().matches('TRAIT') && it.inE().count() == 0}).coun
t()"
;
return
"g.V().has('__type
Name', within(%s)).has('__state', 'ACTIVE').groupCount().by('__typeName').toLis
t()"
;
case
ENTITY_
COUNT
_METRIC:
case
ENTITY_
DELETED
_METRIC:
return
"g.V().has('__
superTypeNames', within(['Referenceable'])).coun
t()"
;
return
"g.V().has('__
typeName', within(%s)).has('__state', 'DELETED').groupCount().by('__typeName').toLis
t()"
;
case
EXPORT_TYPE_STARTS_WITH:
case
EXPORT_TYPE_STARTS_WITH:
return
"g.V().has('__typeName',typeName).filter({it.get().value(attrName).startsWith(attrValue)}).has('__guid').values('__guid').toList()"
;
return
"g.V().has('__typeName',typeName).filter({it.get().value(attrName).startsWith(attrValue)}).has('__guid').values('__guid').toList()"
;
case
EXPORT_TYPE_ENDS_WITH:
case
EXPORT_TYPE_ENDS_WITH:
...
...
repository/src/main/java/org/apache/atlas/util/AtlasGremlinQueryProvider.java
View file @
4a938d87
...
@@ -34,8 +34,8 @@ public abstract class AtlasGremlinQueryProvider {
...
@@ -34,8 +34,8 @@ public abstract class AtlasGremlinQueryProvider {
// Metrics related Queries
// Metrics related Queries
TYPE_COUNT_METRIC
,
TYPE_COUNT_METRIC
,
TYPE_UNUSED_COUNT_METRIC
,
TYPE_UNUSED_COUNT_METRIC
,
ENTITY_COUNT_METRIC
,
TAG_COUNT_METRIC
,
TAG_COUNT_METRIC
,
ENTITY_ACTIVE_METRIC
,
ENTITY_DELETED_METRIC
,
ENTITY_DELETED_METRIC
,
ENTITIES_PER_TYPE_METRIC
,
ENTITIES_PER_TYPE_METRIC
,
TAGGED_ENTITIES_METRIC
,
TAGGED_ENTITIES_METRIC
,
...
...
repository/src/test/java/org/apache/atlas/services/MetricsServiceTest.java
View file @
4a938d87
...
@@ -17,19 +17,16 @@
...
@@ -17,19 +17,16 @@
*/
*/
package
org
.
apache
.
atlas
.
services
;
package
org
.
apache
.
atlas
.
services
;
import
org.apache.atlas.TestModules
;
import
org.apache.atlas.exception.AtlasBaseException
;
import
org.apache.atlas.exception.AtlasBaseException
;
import
org.apache.atlas.model.metrics.AtlasMetrics
;
import
org.apache.atlas.model.metrics.AtlasMetrics
;
import
org.apache.atlas.repository.graphdb.AtlasGraph
;
import
org.apache.atlas.repository.graphdb.AtlasGraph
;
import
org.apache.atlas.runner.LocalSolrRunner
;
import
org.apache.atlas.runner.LocalSolrRunner
;
import
org.apache.atlas.type.AtlasTypeRegistry
;
import
org.apache.atlas.type.AtlasTypeRegistry
;
import
org.apache.atlas.util.AtlasGremlin3QueryProvider
;
import
org.apache.commons.configuration.Configuration
;
import
org.apache.commons.configuration.Configuration
;
import
org.mockito.invocation.InvocationOnMock
;
import
org.mockito.stubbing.Answer
;
import
org.testng.SkipException
;
import
org.testng.SkipException
;
import
org.testng.annotations.AfterClass
;
import
org.testng.annotations.AfterClass
;
import
org.testng.annotations.BeforeClass
;
import
org.testng.annotations.BeforeClass
;
import
org.testng.annotations.Guice
;
import
org.testng.annotations.Test
;
import
org.testng.annotations.Test
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
...
@@ -45,34 +42,40 @@ import static org.mockito.Mockito.*;
...
@@ -45,34 +42,40 @@ import static org.mockito.Mockito.*;
import
static
org
.
testng
.
Assert
.
assertEquals
;
import
static
org
.
testng
.
Assert
.
assertEquals
;
import
static
org
.
testng
.
Assert
.
assertNotNull
;
import
static
org
.
testng
.
Assert
.
assertNotNull
;
@Guice
(
modules
=
TestModules
.
TestOnlyModule
.
class
)
public
class
MetricsServiceTest
{
public
class
MetricsServiceTest
{
private
Configuration
mockConfig
=
mock
(
Configuration
.
class
);
private
Configuration
mockConfig
=
mock
(
Configuration
.
class
);
private
AtlasTypeRegistry
mockTypeRegistry
=
mock
(
AtlasTypeRegistry
.
class
);
private
AtlasTypeRegistry
mockTypeRegistry
=
mock
(
AtlasTypeRegistry
.
class
);
private
AtlasGraph
mockGraph
=
mock
(
AtlasGraph
.
class
);
private
AtlasGraph
mockGraph
=
mock
(
AtlasGraph
.
class
);
private
MetricsService
metricsService
;
private
MetricsService
metricsService
;
private
List
<
Map
>
mockMap
List
=
new
ArrayList
<>();
private
List
<
Map
>
activeEntityCount
List
=
new
ArrayList
<>();
private
Number
mockCount
=
10
;
private
List
<
Map
>
deletedEntityCountList
=
new
ArrayList
<>()
;
@BeforeClass
@BeforeClass
public
void
init
()
throws
Exception
{
public
void
init
()
throws
Exception
{
try
{
try
{
Map
<
String
,
Object
>
mockMap
=
new
HashMap
<>();
Map
<
String
,
Object
>
activeEntityCount
=
new
HashMap
<>();
mockMap
.
put
(
"a"
,
1
);
activeEntityCount
.
put
(
"a"
,
1
);
mockMap
.
put
(
"b"
,
2
);
activeEntityCount
.
put
(
"b"
,
2
);
mockMap
.
put
(
"c"
,
3
);
activeEntityCount
.
put
(
"d"
,
5
);
mockMapList
.
add
(
mockMap
);
activeEntityCount
.
put
(
"e"
,
10
);
activeEntityCount
.
put
(
"f"
,
15
);
activeEntityCountList
.
add
(
activeEntityCount
);
Map
<
String
,
Object
>
deletedEntityCount
=
new
HashMap
<>();
deletedEntityCount
.
put
(
"b"
,
5
);
deletedEntityCountList
.
add
(
deletedEntityCount
);
when
(
mockConfig
.
getInt
(
anyString
(),
anyInt
())).
thenReturn
(
5
);
when
(
mockConfig
.
getInt
(
anyString
(),
anyInt
())).
thenReturn
(
5
);
assertEquals
(
mockConfig
.
getInt
(
"test"
,
1
),
5
);
assertEquals
(
mockConfig
.
getInt
(
"test"
,
1
),
5
);
when
(
mockConfig
.
getString
(
anyString
(),
anyString
()))
when
(
mockConfig
.
getString
(
anyString
(),
anyString
())).
thenReturn
(
"toList()"
,
"toList()"
);
.
thenReturn
(
"count()"
,
"count()"
,
"count()"
,
"count()"
,
"count()"
,
"toList()"
,
"count()"
,
"toList()"
);
when
(
mockTypeRegistry
.
getAllTypeNames
()).
thenReturn
(
Arrays
.
asList
(
"a"
,
"b"
,
"c"
,
"d"
,
"e"
,
"f"
));
when
(
mockTypeRegistry
.
getAllEntityDefNames
()).
thenReturn
(
Arrays
.
asList
(
"a"
,
"b"
,
"c"
));
when
(
mockTypeRegistry
.
getAllEntityDefNames
()).
thenReturn
(
Arrays
.
asList
(
"a"
,
"b"
,
"c"
));
when
(
mockTypeRegistry
.
getAllEntityDefNames
()).
thenReturn
(
Arrays
.
asList
(
"a"
,
"b"
,
"c"
));
when
(
mockTypeRegistry
.
getAllClassificationDefNames
()).
thenReturn
(
Arrays
.
asList
(
"d"
,
"e"
,
"f"
));
// when(mockTypeRegistry.getAllEntityDefNames()).thenReturn(Arrays.asList("a", "b", "c"));
setupMockGraph
();
setupMockGraph
();
metricsService
=
new
MetricsService
(
mockConfig
,
mockGraph
);
metricsService
=
new
MetricsService
(
mockConfig
,
mockGraph
,
mockTypeRegistry
,
new
AtlasGremlin3QueryProvider
()
);
}
}
catch
(
Exception
e
)
{
catch
(
Exception
e
)
{
throw
new
SkipException
(
"MetricsServicesTest: init failed!"
,
e
);
throw
new
SkipException
(
"MetricsServicesTest: init failed!"
,
e
);
...
@@ -88,16 +91,9 @@ public class MetricsServiceTest {
...
@@ -88,16 +91,9 @@ public class MetricsServiceTest {
private
void
setupMockGraph
()
throws
AtlasBaseException
{
private
void
setupMockGraph
()
throws
AtlasBaseException
{
if
(
mockGraph
==
null
)
mockGraph
=
mock
(
AtlasGraph
.
class
);
if
(
mockGraph
==
null
)
mockGraph
=
mock
(
AtlasGraph
.
class
);
when
(
mockGraph
.
executeGremlinScript
(
anyString
(),
eq
(
false
))).
thenAnswer
(
new
Answer
<
Object
>()
{
when
(
mockGraph
.
executeGremlinScript
(
anyString
(),
eq
(
false
)))
@Override
.
thenReturn
(
activeEntityCountList
)
public
Object
answer
(
InvocationOnMock
invocationOnMock
)
throws
Throwable
{
.
thenReturn
(
deletedEntityCountList
);
if
(((
String
)
invocationOnMock
.
getArguments
()[
0
]).
contains
(
"count()"
))
{
return
mockCount
;
}
else
{
return
mockMapList
;
}
}
});
}
}
@Test
@Test
...
@@ -105,29 +101,28 @@ public class MetricsServiceTest {
...
@@ -105,29 +101,28 @@ public class MetricsServiceTest {
assertNotNull
(
metricsService
);
assertNotNull
(
metricsService
);
AtlasMetrics
metrics
=
metricsService
.
getMetrics
(
false
);
AtlasMetrics
metrics
=
metricsService
.
getMetrics
(
false
);
assertNotNull
(
metrics
);
assertNotNull
(
metrics
);
Number
aCount
=
metrics
.
getMetric
(
"entity"
,
"a"
);
Map
activeMetrics
=
(
Map
)
metrics
.
getMetric
(
"entity"
,
"entityActive"
);
assertNotNull
(
aCount
);
assertNotNull
(
activeMetrics
);
assertEquals
(
aCount
,
1
);
assertEquals
(
activeMetrics
.
get
(
"a"
),
1
);
assertEquals
(
activeMetrics
.
get
(
"b"
),
2
);
Number
bCount
=
metrics
.
getMetric
(
"entity"
,
"b"
);
Map
deletedMetrics
=
(
Map
)
metrics
.
getMetric
(
"entity"
,
"entityDeleted"
);
assertNotNull
(
bCount
);
assertEquals
(
deletedMetrics
.
get
(
"b"
),
5
);
assertEquals
(
bCount
,
2
);
Number
cCount
=
metrics
.
getMetric
(
"entity"
,
"c"
);
Number
unusedTypeCount
=
metrics
.
getNumericMetric
(
"general"
,
"typeUnusedCount"
);
assertNotNull
(
cCount
);
assertEquals
(
unusedTypeCount
,
1
);
assertEquals
(
cCount
,
3
);
Number
aTags
=
metrics
.
getMetric
(
"tag"
,
"a"
);
Number
cCount
=
metrics
.
getNumericMetric
(
"general"
,
"entityCount"
);
assertNotNull
(
aTags
);
assertEquals
(
cCount
,
8
);
assertEquals
(
aTags
,
1
);
Number
bTags
=
metrics
.
getMetric
(
"tag"
,
"b"
);
Number
aTags
=
(
Number
)
metrics
.
getMetric
(
"general"
,
"tagCount"
);
assertNotNull
(
bTags
);
assertEquals
(
aTags
,
3
);
assertEquals
(
bTags
,
2
);
Number
cTags
=
metrics
.
getMetric
(
"tag"
,
"c"
);
Map
taggedEntityMetric
=
(
Map
)
metrics
.
getMetric
(
"tag"
,
"tagEntities"
);
assertNotNull
(
cTags
);
assertNotNull
(
taggedEntityMetric
);
assertEquals
(
cTags
,
3
);
assertEquals
(
taggedEntityMetric
.
get
(
"d"
),
5
);
assertEquals
(
taggedEntityMetric
.
get
(
"e"
),
10
);
assertEquals
(
taggedEntityMetric
.
get
(
"f"
),
15
);
verify
(
mockGraph
,
atLeastOnce
()).
executeGremlinScript
(
anyString
(),
anyBoolean
());
verify
(
mockGraph
,
atLeastOnce
()).
executeGremlinScript
(
anyString
(),
anyBoolean
());
...
...
webapp/src/main/java/org/apache/atlas/web/resources/AdminResource.java
View file @
4a938d87
...
@@ -99,9 +99,9 @@ public class AdminResource {
...
@@ -99,9 +99,9 @@ public class AdminResource {
private
Response
version
;
private
Response
version
;
private
static
Configuration
atlasProperties
;
private
final
ServiceState
serviceState
;
private
final
ServiceState
serviceState
;
private
final
MetricsService
metricsService
;
private
final
MetricsService
metricsService
;
private
static
Configuration
atlasProperties
;
private
final
ExportService
exportService
;
private
final
ExportService
exportService
;
private
final
ImportService
importService
;
private
final
ImportService
importService
;
private
final
SearchTracker
activeSearches
;
private
final
SearchTracker
activeSearches
;
...
...
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