Skip to content

Commit

Permalink
Add reference validation
Browse files Browse the repository at this point in the history
Change-Id: I3a0da8995a0bff989f9536392f106e4ddc7b316a
Closes-Bug: #1786237
  • Loading branch information
dmarkiewicz committed Aug 13, 2018
1 parent 75c0763 commit 395fa7a
Show file tree
Hide file tree
Showing 21 changed files with 238 additions and 42 deletions.
4 changes: 2 additions & 2 deletions o11nplugin-contrail-config/buildNumber.properties
@@ -1,3 +1,3 @@
#maven.buildNumber.plugin properties file
#Fri Aug 03 14:36:20 CEST 2018
buildNumber=1656
#Mon Aug 13 14:44:21 CEST 2018
buildNumber=1732
Expand Up @@ -46,6 +46,7 @@ val listLabelTags = "listLabelTags"
val matchesSecurityScope = "matchesSecurityScope"
val defaultConnection = "defaultConnection"
val hasBackrefs = "hasBackrefs"
val isNotReferencedBy = "isNotReferencedBy"

val portOfVCVirtualMachine = "portOfVCVirtualMachine"
val networkOfVCPortGroup = "networkOfVCPortGroup"
Expand Up @@ -33,13 +33,13 @@ val getPrefix = "^get".toRegex()
val backRefsPostfix = "$BackRefs$".toRegex()

val childReferencePattern = "get($className)s".toRegex()
val forwardReferencePattern = "get($className)".toRegex()
val referencePattern = "get($className)".toRegex()
val backReferencePattern = "get($className)$BackRefs".toRegex()

val referencePatterns = sequenceOf(
backReferencePattern,
childReferencePattern,
forwardReferencePattern
referencePattern
)

val Class<*>.refPropertyName get() =
Expand Down Expand Up @@ -85,7 +85,7 @@ val Method.childClassName get() =
referredClassName(childReferencePattern)

val Method.referenceClassName get() =
referredClassName(forwardReferencePattern)
referredClassName(referencePattern)

val Method.backReferenceClassName get() =
referredClassName(backReferencePattern)
Expand Down
Expand Up @@ -161,7 +161,12 @@ val reversedRelations = setOf(
)

val readUponQuery = setOf(
the<FirewallRule>()
the<ApplicationPolicySet>(),
the<FirewallPolicy>(),
the<FirewallRule>(),
the<AddressGroup>(),
the<ServiceGroup>(),
the<Tag>()
)

val validateSecurityScope = setOf(
Expand Down
Expand Up @@ -8,6 +8,7 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
import java.util.stream.Collectors;
import net.juniper.contrail.vro.model.Connection;
import net.juniper.contrail.vro.model.Executor;
import net.juniper.contrail.vro.format.*;
Expand Down Expand Up @@ -62,6 +63,10 @@ public class ${className}
return (${modelClass.canonicalName}) _target;
}

public String getObjectClassName() {
return __getTarget().getClass().getSimpleName();
}

<#list constructors as c> <#if !c.extensionClass??>
<#if !(c.params?has_content)><#assign defaultConstructor = true></#if>
<@compress single_line=true>public ${className}(<@params c />)<@thrown c /> {</@compress>
Expand Down Expand Up @@ -236,6 +241,12 @@ public class ${className}

</#list>

<#list backrefs as bref>
public boolean isReferencedBy${bref.className}(${bref.className}_Wrapper reference) {
return ${bref.pluginMethodName}().stream().map(it -> it.getUuid()).collect(Collectors.toList()).contains(reference.getUuid());
}
</#list>

public Integer backrefCount() {
int count = 0;
<#list backrefs as bref>
Expand Down
Expand Up @@ -8,14 +8,14 @@ import net.juniper.contrail.api.ApiPropertyBase
import net.juniper.contrail.api.types.SubnetType
import net.juniper.contrail.vro.config.constants.Contrail
import net.juniper.contrail.vro.config.constants.item
import net.juniper.contrail.vro.config.isValidSubnet
import net.juniper.contrail.vro.config.readSubnet
import net.juniper.contrail.vro.workflows.dsl.ParameterAggregator
import net.juniper.contrail.vro.generator.model.Property
import net.juniper.contrail.vro.workflows.dsl.fromAction
import net.juniper.contrail.vro.workflows.model.string
import net.juniper.contrail.vro.schema.Schema
import net.juniper.contrail.vro.schema.simpleTypeConstraints
import net.juniper.contrail.vro.workflows.custom.isSubnet

val Class<*>.hasCustomInput get() =
customProperties.containsKey(this)
Expand Down Expand Up @@ -47,7 +47,7 @@ private object CustomSubnetType : CustomProperty<SubnetType> {
override fun Property.setup(builder: ParameterAggregator, schema: Schema, createMode: Boolean, propertyPath: () -> String) {
builder.parameter(propertyName, string) {
description = description(schema)
validWhen = validationActionCallTo(isValidSubnet)
validWhen = isSubnet()
if (!createMode)
dataBinding = fromAction(readSubnet, string) { parameter(item).string("${propertyPath().preparePrefix()}$propertyName") }
additionalQualifiers += schema.simpleTypeConstraints(parent, propertyName, ignoreMissing = true)
Expand Down
Expand Up @@ -11,16 +11,18 @@ import net.juniper.contrail.vro.config.isA
import net.juniper.contrail.vro.config.needsSecurityScopeValidation
import net.juniper.contrail.vro.config.propertyValue
import net.juniper.contrail.vro.generator.model.ForwardRelation
import net.juniper.contrail.vro.schema.Schema
import net.juniper.contrail.vro.workflows.custom.isNotReferencedBy
import net.juniper.contrail.vro.workflows.custom.matchesSecurityScope
import net.juniper.contrail.vro.workflows.dsl.WhenNonNull
import net.juniper.contrail.vro.workflows.dsl.WorkflowDefinition
import net.juniper.contrail.vro.workflows.dsl.actionCallTo
import net.juniper.contrail.vro.workflows.dsl.and
import net.juniper.contrail.vro.workflows.dsl.asBrowserRoot
import net.juniper.contrail.vro.workflows.dsl.parentConnection
import net.juniper.contrail.vro.workflows.dsl.withScript
import net.juniper.contrail.vro.workflows.dsl.workflow
import net.juniper.contrail.vro.workflows.dsl.WhenNonNull
import net.juniper.contrail.vro.workflows.dsl.asBrowserRoot
import net.juniper.contrail.vro.workflows.dsl.actionCallTo
import net.juniper.contrail.vro.workflows.model.reference
import net.juniper.contrail.vro.schema.Schema
import net.juniper.contrail.vro.workflows.custom.matchesSecurityScope
import net.juniper.contrail.vro.workflows.dsl.parentConnection
import net.juniper.contrail.vro.workflows.util.addRelationWorkflowName
import net.juniper.contrail.vro.workflows.util.childDescriptionInCreateRelation
import net.juniper.contrail.vro.workflows.util.childDescriptionInRemoveRelation
Expand All @@ -47,8 +49,10 @@ fun addReferenceWorkflow(relation: ForwardRelation, schema: Schema): WorkflowDef
description = schema.childDescriptionInCreateRelation(parentClass, childClass, ignoreMissing = true)
mandatory = true
browserRoot = item.parentConnection
if (childClass.needsSecurityScopeValidation)
validWhen = matchesSecurityScope(item, directValidation)
validWhen = if (childClass.needsSecurityScopeValidation)
matchesSecurityScope(item, directValidation) and isNotReferencedBy(item)
else
isNotReferencedBy(item)
}
}
}
Expand Down
Expand Up @@ -34,6 +34,7 @@ import net.juniper.contrail.vro.workflows.dsl.NullStateOfProperty
import net.juniper.contrail.vro.workflows.dsl.VisibilityCondition
import net.juniper.contrail.vro.workflows.dsl.WhenNonNull
import net.juniper.contrail.vro.workflows.dsl.and
import net.juniper.contrail.vro.workflows.dsl.asValidationCondition
import net.juniper.contrail.vro.workflows.model.array
import net.juniper.contrail.vro.workflows.model.boolean
import net.juniper.contrail.vro.workflows.model.createDunesProperties
Expand Down Expand Up @@ -126,7 +127,7 @@ private fun Property.toPrimitiveParameter(builder: ParameterAggregator, schema:
if (customValidationAction == null)
additionalQualifiers += schema.simpleTypeConstraints(parent, propertyName, ignoreMissing = true)
else
validWhen = validationActionCallTo(customValidationAction)
validWhen = validationActionCallTo(customValidationAction).asValidationCondition()
}
}

Expand Down
Expand Up @@ -48,6 +48,8 @@ class ScriptTestEngine {
engine.context.setAttribute(name, attr, scope)
}

fun evalCondition(condition: String) = engine.eval(condition)

fun evalFunction(function : String) {
engine.eval(function, engine.context)
}
Expand Down
@@ -0,0 +1,42 @@
package net.juniper.contrail.vro.tests.actions

import net.juniper.contrail.vro.gen.NetworkPolicy_Wrapper
import net.juniper.contrail.vro.tests.workflows.WorkflowSpec
import static net.juniper.contrail.vro.config.Actions.isNotReferencedBy

class IsNotReferencedBySpec extends WorkflowSpec implements ValidationAsserts{
def isNotReferencedByAction = actionFromScript(isNotReferencedBy)
def errorMessage = "Already referenced"

def "null child results in success validation"() {
given: "child is set to null"
def parent = null
def child = null
when: "executing validation script"
def result = engine.invokeFunction(isNotReferencedByAction, child, parent)
then: "it succeeds"
validationSuccess(result)
}

def "child which is not already referenced by parent results in success validation"() {
given: "parent is set to some virtual network and child is set to some policy"
def parent = dependencies.someVirtualNetwork()
def child = dependencies.someNetworkPolicy()
when: "executing validation script"
def result = engine.invokeFunction(isNotReferencedByAction, child, parent)
then: "it succeeds"
validationSuccess(result)
}

def "child which is already referenced by parent results in failure validation with message"() {
given: "parent is set to some virtual network and child is set to some policy"
def parent = dependencies.someVirtualNetwork()
def child = Mock(NetworkPolicy_Wrapper)
child.isReferencedByVirtualNetwork(parent) >> true
when: "executing validation script"
def result = engine.invokeFunction(isNotReferencedByAction, child, parent)
then: "it fails with message"
validationFailureWith(result, errorMessage)
}

}
@@ -0,0 +1,59 @@
package net.juniper.contrail.vro.tests.dsl

import net.juniper.contrail.vro.tests.ScriptTestEngine
import net.juniper.contrail.vro.tests.actions.ValidationAsserts
import net.juniper.contrail.vro.workflows.dsl.AlwaysValid
import net.juniper.contrail.vro.workflows.dsl.AlwaysInvalid
import net.juniper.contrail.vro.workflows.dsl.ValidationConditionKt
import spock.lang.Specification

class ValidationConjunctionSpec extends Specification implements ValidationAsserts{
def errorMessage = "Error message"
def anotherErrorMessage = "Another error message"
def scriptTestEngine = new ScriptTestEngine()

def "error message and null results in error message"() {
given: "arguments"
def firstArgument = new AlwaysInvalid(errorMessage)
def secondArgument = new AlwaysValid()
when: "conditions are evaluated"
def complexCondition = ValidationConditionKt.and(firstArgument, secondArgument)
def result = scriptTestEngine.evalCondition(complexCondition.stringCondition)
then: "validation is unsuccessful with error message"
validationFailureWith(result, errorMessage)
}

def "null and error message results in error message"() {
given: "arguments"
def firstArgument = new AlwaysValid()
def secondArgument = new AlwaysInvalid(errorMessage)
when: "conditions are evaluated"
def complexCondition = ValidationConditionKt.and(firstArgument, secondArgument)
def result = scriptTestEngine.evalCondition(complexCondition.stringCondition)
then: "validation is unsuccessful with error message"
validationFailureWith(result, errorMessage)
}

def "error message and another error message results in first error message"() {
given: "arguments"
def firstArgument = new AlwaysInvalid(errorMessage)
def secondArgument = new AlwaysInvalid(anotherErrorMessage)
when: "conditions are evaluated"
def complexCondition = ValidationConditionKt.and(firstArgument, secondArgument)
def result = scriptTestEngine.evalCondition(complexCondition.stringCondition)
then: "validation is unsuccessful with first error message"
validationFailureWith(result, errorMessage)
}

def "successful validation and successful validation results in successful validation"() {
given: "arguments"
def firstArgument = new AlwaysValid()
def secondArgument = new AlwaysValid()
when: "conditions are evaluated"
def complexCondition = ValidationConditionKt.and(firstArgument, secondArgument)
def result = scriptTestEngine.evalCondition(complexCondition.stringCondition)
then: "validation is successful"
validationSuccess(result)
}

}
Expand Up @@ -146,10 +146,7 @@ abstract class BasicParameterBuilder<Type: Any>(val parameterName: String, val t
var defaultValue: Type? = null
var dataBinding: DataBinding<Type> = NoDataBinding
val additionalQualifiers = mutableListOf<ParameterQualifier>()
var validWhen: ActionCallBuilder? = null
set(value) {
field = value?.snapshot()
}
var validWhen: ValidationCondition = AlwaysValid

fun validationActionCallTo(actionName: String) =
actionCallTo(actionName).parameter(parameterName)
Expand All @@ -173,8 +170,11 @@ abstract class BasicParameterBuilder<Type: Any>(val parameterName: String, val t
dataBinding.qualifier?.let {
add(it)
}
validWhen?.let {
add(validationQualifier(it.create()))
validWhen.let {
when (it) {
AlwaysValid -> Unit
else -> add(validationConditionQualifier(it))
}
}
}

Expand Down
Expand Up @@ -68,12 +68,12 @@ fun regexQualifier(pattern: String) =
fun visibilityConditionQualifier(condition: VisibilityCondition) =
ognlQualifier(QualifierName.visible, boolean, condition.stringCondition)

fun validationConditionQualifier(condition: ValidationCondition) =
ognlQualifier(QualifierName.ognlValidator, boolean, condition.stringCondition)

fun <T : Any> listFromAction(action: ActionCall, type: ParameterType<T>) =
ognlQualifier(QualifierName.linkedEnumeration, type, action.ognl)

fun validationQualifier(action: ActionCall) =
ognlQualifier(QualifierName.ognlValidator, any, action.ognl)

fun selectWith(selector: ReferenceSelector) =
staticQualifier(QualifierName.showSelectAs, string, selector.toString())

Expand Down
@@ -0,0 +1,36 @@
package net.juniper.contrail.vro.workflows.dsl

sealed class ValidationCondition {
abstract val stringCondition: String
}

object AlwaysValid : ValidationCondition() {
override val stringCondition = "null"
}

class AlwaysInvalid(errorMesssage: String) : ValidationCondition() {
override val stringCondition: String = "\"$errorMesssage\""
}

private class ValidationFromAction(actionCallBuilder: ActionCallBuilder) : ValidationCondition() {
override val stringCondition = actionCallBuilder.create().ognl
}

private class ValidationConditionConjunction(vararg conditions: ValidationCondition) : ValidationCondition() {
// Successful validation returns null which is a falsy value in ognl
// Unsuccessful validation returns string which is a truthy value in ognl
// && operator finishes evaluating on first falsy value and won't evaluate following validations
// || on the other hand stops at first truthy value(which is an error) and returns it
// Because we want all the conditions to be met, we use || operator
override val stringCondition: String = conditions.joinToString(" || ") { "(${it.stringCondition})" }
}

infix fun ValidationCondition.and(other: ValidationCondition): ValidationCondition = when {
this == AlwaysValid -> other
other == AlwaysValid -> this
else -> ValidationConditionConjunction(this, other)
}

fun ActionCallBuilder.asValidationCondition(): ValidationCondition {
return ValidationFromAction(this)
}
@@ -0,0 +1,13 @@

if (child === null) {
return null
}

methodName = "isReferencedBy" + parent.getObjectClassName()
toInvoke = "child." + methodName + "(parent)";

if (eval(toInvoke)) {
return "Already referenced"
}

return null
Expand Up @@ -10,6 +10,7 @@ import net.juniper.contrail.vro.config.constants.child
import net.juniper.contrail.vro.config.constants.item
import net.juniper.contrail.vro.schema.Schema
import net.juniper.contrail.vro.workflows.dsl.WorkflowDefinition
import net.juniper.contrail.vro.workflows.dsl.and
import net.juniper.contrail.vro.workflows.dsl.parentConnection
import net.juniper.contrail.vro.workflows.model.reference
import net.juniper.contrail.vro.workflows.util.addRelationWorkflowName
Expand All @@ -30,6 +31,8 @@ internal fun addFirewallPolicyToAPS(schema: Schema): WorkflowDefinition {
description = schema.childDescriptionInCreateRelation<ApplicationPolicySet, FirewallPolicy>()
mandatory = true
browserRoot = item.parentConnection
validWhen = matchesSecurityScope(item, false) and isNotReferencedBy(item)

}
}
}

0 comments on commit 395fa7a

Please sign in to comment.