Skip to content

Instantly share code, notes, and snippets.

  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save ghenzler/a44d9c75323b59ba80a39290abe2a133 to your computer and use it in GitHub Desktop.
//-------------------------------------
// *** Deep Group Hierarchies
// *** with functionality fragments
//-------------------------------------
// Breaklet Config
//-------------------------------------
def breakForDurationInSec = 60
def componentsOnTestPage = 10
def funcFragmentsPerIteration = 5
def subFuncFragments = 5
def contentBasePath = "/content/test"
def acToolDir = "/apps/test-deep-group-hierarchies"
def sessionUser = 'admin'
def sessionPassword = 'admin'
def breakletUser = 'breakletuser'
def breakletPassword = 'breakletpassword'
def breakletUser2 = 'breakletuser2'
def breakletPassword2 = 'breakletpassword'
def noPaddingWidth = 4
//-------------------------------------
// Breaklet
//-------------------------------------
new Breaklet(this, breakForDurationInSec, sessionUser, sessionPassword).run { breaklet, currentIt, itSession ->
def contentBasePathJcrContent = contentBasePath + "/jcr:content"
def contentBasePathJcrContentInvisibleSubnode = contentBasePathJcrContent + "/compinvisible"
def acToolUserConfigs = []
def acToolGroupConfigs = []
def acToolAceConfigs = []
acToolGroupConfigs.add("""
| - breaklet-deny:
| - isMemberOf:
| path: /home/groups/breakletfragments
""".stripMargin())
acToolGroupConfigs.add("""
| - breaklet-allow:
| - isMemberOf:
| path: /home/groups/breakletfragments
""".stripMargin())
acToolAceConfigs.add("""
|
| - breaklet-deny:
|
| - path: ${contentBasePath}
| permission: deny
| privileges: jcr:all
|
|
""".stripMargin())
acToolAceConfigs.add("""
|
| - breaklet-allow:
|
| - path: ${contentBasePath}
| permission: allow
| privileges: jcr:read
|
""".stripMargin())
def userIsMemberOfSeesAll = []
def userIsMemberOfSees30Percent = []
def pageContentNode = JcrUtils.getOrCreateByPath(contentBasePathJcrContent, "cq:Page", "cq:PageContent", itSession, false)
pageContentNode.setProperty("sling:resourceType", "wcm/foundation/components/responsivegrid")
for(int componentNo=1; componentNo<= componentsOnTestPage; componentNo++) {
String compNoPadded = StringUtils.leftPad(componentNo+"", noPaddingWidth, "0")
def contentComponentPath = contentBasePathJcrContent + "/comp"+compNoPadded
def contentComponentNode = JcrUtils.getOrCreateByPath(contentComponentPath, "nt:unstructured", "nt:unstructured", itSession, false)
contentComponentNode.setProperty("sling:resourceType", "wcm/foundation/components/responsivegrid")
def funcFragmentNo = funcFragmentsPerIteration * currentIt
for(int funcFragmentItNo = 1; funcFragmentItNo <=funcFragmentNo; funcFragmentItNo++) {
String funcFragmentNoPadded = StringUtils.leftPad(funcFragmentItNo+"", noPaddingWidth, "0")
def contentPathFragment = contentComponentPath + "/frag" + funcFragmentNoPadded
def funcFragmentNode = JcrUtils.getOrCreateByPath(contentPathFragment, "nt:unstructured", "nt:unstructured", itSession, false)
funcFragmentNode.setProperty("sling:resourceType", "wcm/foundation/components/responsivegrid")
def contentPathFragmentBase = contentPathFragment + "/base"
def funcFragmentNodeBase = JcrUtils.getOrCreateByPath(contentPathFragmentBase, "nt:unstructured", "nt:unstructured", itSession, false)
funcFragmentNodeBase.setProperty("sling:resourceType", "wcm/foundation/components/text")
def funcFragmentDescription = "Base Func Fragment Comp "+componentNo + " Fragment "+ funcFragmentItNo
funcFragmentNodeBase.setProperty("text", funcFragmentDescription)
String funcFragmentGroupName = "g-comp"+compNoPadded+"-funcfragment"+funcFragmentNoPadded
def subFragmentNames = []
def subFuncFragmentNo = subFuncFragments
for(int subFuncFragmentItNo = 1; subFuncFragmentItNo <=subFuncFragmentNo; subFuncFragmentItNo++) {
String subFuncFragmentNoPadded = StringUtils.leftPad(subFuncFragmentItNo+"", noPaddingWidth, "0")
def contentPathSubFragment = contentPathFragment + "/sub" + subFuncFragmentNoPadded
def subFuncFragmentNode = JcrUtils.getOrCreateByPath(contentPathSubFragment, "nt:unstructured", "nt:unstructured", itSession, false)
subFuncFragmentNode.setProperty("sling:resourceType", "wcm/foundation/components/text")
def subFuncFragmentDescription = "--- " + funcFragmentDescription+" - Sub "+ subFuncFragmentItNo
subFuncFragmentNode.setProperty("text", subFuncFragmentDescription)
String subFuncFragmentGroupName = "g-comp"+compNoPadded+"-funcfragment"+funcFragmentNoPadded + "-sub"+subFuncFragmentNoPadded
subFragmentNames.add(subFuncFragmentGroupName)
acToolGroupConfigs.add("""
|
| - ${subFuncFragmentGroupName}:
| - isMemberOf:
| virtual: false
| path: /home/groups/breakletfragments/c${compNoPadded}-f${funcFragmentNoPadded}
|
""".stripMargin())
acToolAceConfigs.add("""
|
| - breaklet-deny:
|
| - path: ${contentPathSubFragment}
| permission: deny
| privileges: jcr:all
|
""".stripMargin())
acToolAceConfigs.add("""
|
| - ${subFuncFragmentGroupName}:
|
| - path: ${contentPathSubFragment}
| permission: allow
| privileges: jcr:read
|
""".stripMargin())
}
String isMemberOf = StringUtils.join(subFragmentNames,",")
acToolGroupConfigs.add("""
|
| - ${funcFragmentGroupName}:
| - isMemberOf: ${isMemberOf}
| path: /home/groups/breakletfragments/c${compNoPadded}-f${funcFragmentNoPadded}
|
""".stripMargin())
acToolAceConfigs.add("""
|
| - breaklet-deny:
|
| - path: ${contentPathFragmentBase}
| permission: deny
| privileges: jcr:all
|
""".stripMargin())
acToolAceConfigs.add("""
|
| - ${funcFragmentGroupName}:
|
| - path: ${contentPathFragmentBase}
| permission: allow
| privileges: jcr:read
|
""".stripMargin())
userIsMemberOfSeesAll.add(funcFragmentGroupName)
if(funcFragmentItNo % 10 == 3 ) {
userIsMemberOfSees30Percent.add(funcFragmentGroupName)
}
}
}
def userIsMemberOfSeesAllStr = StringUtils.join(userIsMemberOfSeesAll, ",")
acToolUserConfigs.add("""
| - ${breakletUser}:
| - isMemberOf: breaklet-deny,breaklet-allow,${userIsMemberOfSeesAllStr}
| password: ${breakletPassword}
| path: /home/users/breaklet
""".stripMargin())
def userIsMemberOfSees30PercentStr = StringUtils.join(userIsMemberOfSees30Percent, ",")
acToolUserConfigs.add("""
| - ${breakletUser2}:
| - isMemberOf: breaklet-deny,breaklet-allow,${userIsMemberOfSees30PercentStr}
| password: ${breakletPassword2}
| path: /home/users/breaklet
""".stripMargin())
def acToolYaml = "- user_config:\n\n" + StringUtils.join(acToolUserConfigs, "\n") +
"\n\n- group_config:\n\n" + StringUtils.join(acToolGroupConfigs, "\n") +
"\n\n- ace_config:\n\n" + StringUtils.join(acToolAceConfigs, "\n")
def acToolDirNode = JcrUtils.getOrCreateByPath(acToolDir, "nt:folder", "nt:folder", itSession, false)
JcrUtils.putFile(acToolDirNode, "breaklet.yaml", "text/yaml", new ByteArrayInputStream(acToolYaml.getBytes(StandardCharsets.UTF_8)))
itSession.save()
breaklet.logOpFinished("Setup content for "+funcFragmentsPerIteration+ " more fragments (with "+subFuncFragments+" sub fragments each)")
getService("biz.netcentric.cq.tools.actool.api.AcInstallationService").apply(acToolDir, [contentBasePath] as String[])
breaklet.logOpFinished("Applied AC Tool", "", null, true)
def requestPath = contentBasePath+".html?wcmmode=disabled"
def responseText = breaklet.performRequest(requestPath, breakletUser, breakletPassword)
breaklet.logOpFinished("User with all fragments", responseText.length() + " chars received")
responseText = breaklet.performRequest(requestPath, breakletUser2, breakletPassword2)
breaklet.logOpFinished("User with 30% fragments", responseText.length() + " chars received")
responseText = breaklet.performRequest(requestPath, sessionUser, sessionPassword)
breaklet.logOpFinished("Admin request", responseText.length() + " chars received")
acToolYaml = acToolYaml.replaceAll('virtual: false', 'virtual: true')
JcrUtils.putFile(acToolDirNode, "breaklet.yaml", "text/yaml", new ByteArrayInputStream(acToolYaml.getBytes(StandardCharsets.UTF_8)))
itSession.save()
breaklet.logOpFinished("Setup AC yaml with virtual groups")
getService("biz.netcentric.cq.tools.actool.api.AcInstallationService").apply(acToolDir, [contentBasePath] as String[])
breaklet.logOpFinished("Applied AC Tool", "", null, true)
responseText = breaklet.performRequest(requestPath, breakletUser, breakletPassword)
breaklet.logOpFinished("User with all fragments (virtual groups)", responseText.length() + " chars received")
responseText = breaklet.performRequest(requestPath, breakletUser2, breakletPassword2)
breaklet.logOpFinished("User with 30% fragments (virtual groups)", responseText.length() + " chars received")
responseText = breaklet.performRequest(requestPath, sessionUser, sessionPassword)
breaklet.logOpFinished("Admin request (virtual groups)", responseText.length() + " chars received")
}
// DO NOT CHANGE BEYOND THIS POINT - master is https://gist.github.com/ghenzler/58ce57794ba9b5a60f2a2e60160be747 here
// --------------------------------------------------------------------
// Breaklet Base Utilities (to be copied at bottom of other breaklets)
// --------------------------------------------------------------------
import org.apache.jackrabbit.commons.JcrUtils
import org.apache.commons.lang3.StringUtils
import java.util.concurrent.ThreadLocalRandom
import javax.jcr.query.*
import org.apache.sling.jcr.api.*
import org.apache.sling.api.resource.observation.*
import java.util.Properties
import java.io.ByteArrayInputStream
import java.nio.charset.StandardCharsets
import org.osgi.service.event.EventConstants
import org.osgi.service.event.EventHandler
import org.osgi.service.event.Event
import org.apache.commons.io.IOUtils
import org.apache.sling.hc.util.FormattingResultLog
class BreakletBrokeException extends RuntimeException {
String opName
String timeSpent
BreakletBrokeException(opName, timeSpent) {
this.opName = opName
this.timeSpent = timeSpent
}
}
class BreakletPanicException extends RuntimeException { }
class Breaklet {
int iterations
int breakForDurationInSec
String user
String password
long lastTimestamp = System.currentTimeMillis()
def script
long currentIteration
String currentItLogMarker
Map<String,List> timings = new LinkedHashMap<String,List>()
Browser browser
def resourceChangeListener = null
def resourceChangeListenerReg = null
def resourceChangeListenerPath = null
def resourceChangeEventHandler = null
def resourceChangeEventHandlerReg = null
def resourceChangeEventHandlerPath = null
Breaklet(def script, int breakForDurationInSec, String user=null, String password=null, int iterations=Integer.MAX_VALUE) {
this.script = script
this.breakForDurationInSec = breakForDurationInSec
this.user = user
this.password = password
this.iterations = iterations
this.browser = new Browser(script, breakForDurationInSec, user, password)
}
def logOpFinished(String opName, String opDescription="", def timeSpentMillis=null, def neverBreak=false) {
script.logMsg(StringUtils.leftPad(currentItLogMarker, 7)+" " + StringUtils.rightPad(getTimeSpent(opName, timeSpentMillis as Long, neverBreak) + ":", 8) + " "+opName + " "+opDescription)
currentItLogMarker = ""
}
def getTimeSpent(String opName, Long timeSpentMillis=null, def neverBreak=false) {
long delta = timeSpentMillis ?: System.currentTimeMillis()-lastTimestamp
def timingSeriesForOp = timings.get(opName)
if(timingSeriesForOp==null) { timings.put(opName, (timingSeriesForOp = new ArrayList<Long>())) }
while(timingSeriesForOp.size() < currentIteration-1) { timingSeriesForOp.add(timingSeriesForOp.size(), '') }
timingSeriesForOp.add((int)currentIteration-1, delta)
String timeSpent = FormattingResultLog.msHumanReadable(delta)
if((delta/1000) > breakForDurationInSec && !neverBreak) {
throw new BreakletBrokeException(opName, timeSpent)
}
lastTimestamp = System.currentTimeMillis()
return timeSpent
}
def run(toRun) {
File currentWorkingDir = new File(System.getProperty('user.dir'))
File panicFile = new File(currentWorkingDir, 'panic')
try {
(1..iterations).each { itNo ->
currentItLogMarker = itNo+":"
def sessionForIteration = null
try {
if(StringUtils.isNotBlank(user)) {
sessionForIteration = getSessionFor(user, password)
}
currentIteration = itNo
def sessionForIt = sessionForIteration ?: script.session
toRun(this, currentIteration, sessionForIt)
if(panicFile.exists()) {
panicFile.renameTo(new File(currentWorkingDir, 'no-panic'))
throw new BreakletPanicException()
}
} finally {
if(sessionForIteration!=null && sessionForIteration.isLive()) {
sessionForIteration.logout()
}
}
}
script.logMsg("\n\nBreaklet survived - "+iterations+" iterations finished without exceeding the threshold\n")
} catch(BreakletBrokeException e) {
script.logMsg("\n\nBreaklet broke at iteration "+currentIteration+": Operation '"+e.opName+"' "+e.timeSpent + " >= "+breakForDurationInSec + "sec\n")
} catch(BreakletPanicException e) {
script.logMsg("\nBreaklet broke due to panic after iteration "+currentIteration+"\n")
} catch(Exception e) {
script.logMsg("\n\nBreaklet broke at iteration "+currentIteration+" with unexpected exception: "+e)
script.log.info("Exception:"+e, e)
} finally {
String result = ""
timings.keySet().each { key ->
result += ""+key+","+ StringUtils.join(timings.get(key),',') + "\n"
}
new File(currentWorkingDir, 'breaklet-result-'+new Date().getTime()+'.csv').text = result
if(resourceChangeListenerReg) {
resourceChangeListenerReg.unregister()
}
if(resourceChangeEventHandlerReg) {
resourceChangeEventHandlerReg.unregister()
}
}
}
def runInBrowser(String puppeteerScript) {
return browser.run(puppeteerScript)
}
def getSessionFor(sessionUser, sessionPassword) {
def repository = script.session.getRepository()
def session = repository.login(new SimpleCredentials(sessionUser, sessionPassword.toCharArray()));
return session
}
def registerResourceChangeListener(paths) {
resourceChangeListenerPath = paths
resourceChangeListener = new ResourceChangeListener() {
int changesReceived = 0
int callsReceived = 0
def allChanges = []
public void onChange(List<ResourceChange> changes) {
changesReceived += changes.size()
callsReceived++
allChanges.addAll(changes)
}
public void reset() {
changesReceived = 0
callsReceived = 0
allChanges = []
}
}
resourceChangeListenerReg = script.bundleContext.registerService("org.apache.sling.api.resource.observation.ResourceChangeListener", resourceChangeListener, new Hashtable([(ResourceChangeListener.PATHS): paths]))
return resourceChangeListener
}
def registerResourceChangeEventHandler(path) {
resourceChangeEventHandlerPath = path
resourceChangeEventHandler = new EventHandler() {
int eventsReceived = 0
def allEvents = []
public void handleEvent(final Event event) {
eventsReceived++
allEvents.addAll(event)
}
public void reset() {
eventsReceived = 0
allEvents = []
}
}
resourceChangeEventHandlerReg = script.bundleContext.registerService("org.osgi.service.event.EventHandler", resourceChangeEventHandler,
new Hashtable([
(EventConstants.EVENT_TOPIC): "org/apache/sling/api/resource/Resource/*",
(EventConstants.EVENT_FILTER): "("+SlingConstants.PROPERTY_PATH+"="+path+"*)",
]))
return resourceChangeEventHandler
}
def waitForResourceChangedEvents(long minimumExpectedChanges, long maxWaitInSec=300, String opName=null) {
boolean timeout = false
int waitCounter = 0
while((resourceChangeListener && resourceChangeListener.changesReceived < minimumExpectedChanges)
|| (resourceChangeEventHandler && resourceChangeEventHandler.eventsReceived < minimumExpectedChanges)
) {
if((lastTimestamp + maxWaitInSec*1000) < System.currentTimeMillis()) {
timeout = true
break
}
Thread.sleep(50)
}
String resourceChangeListenerResult = resourceChangeListener ? " changesReceived="+resourceChangeListener.changesReceived+" (in "+resourceChangeListener.callsReceived +" calls) " : ""
String resourceChangeEventHandlerResult = resourceChangeEventHandler ? " eventsReceived="+resourceChangeEventHandler.eventsReceived : ""
opName = opName ?: "Wait for resource changed events ("+(resourceChangeListenerPath?:resourceChangeEventHandlerPath)+")"
logOpFinished(opName, (timeout?'Timed out after '+maxWaitInSec+'sec ':'') + resourceChangeListenerResult + resourceChangeEventHandlerResult)
resourceChangeListener?.reset()
resourceChangeEventHandler?.reset()
}
def performRequest(String requestPath, String reqUser=user, String reqPassword=password) {
def con = new URL(script.getBaseUrl()+requestPath).openConnection();
String encoded = Base64.getEncoder().encodeToString((reqUser+":"+reqPassword).getBytes(StandardCharsets.UTF_8));
con.setRequestProperty("Authorization", "Basic "+encoded);
def result = IOUtils.toString(con.getInputStream());
return result
}
}
class Browser {
// config
def script
String user
String password
int breakForDurationInSec
// state
File browserDir
String testJsTemplate
Properties resultProps
Browser(def script, int breakForDurationInSec, String user, String password) {
this.script = script
this.breakForDurationInSec = breakForDurationInSec
this.user = user
this.password = password
}
def init() {
browserDir = new File(System.getProperty('user.dir')+'/puppeteer')
if(!browserDir.exists()) {
script.logMsg("init "+ browserDir)
browserDir.mkdir()
new File(browserDir, 'package.json').text = """
{
"name": "puppeteer-breaklet",
"version": "1.0.0",
"scripts": {
"test": "node test.js"
},
"dependencies": {
"puppeteer": "1.7.0"
}
}
""".stripIndent()
def result = script.exec("npm install", browserDir)
script.logMsg("npm install result: "+ result)
}
testJsTemplate = '''
const puppeteer = require('puppeteer');
let baseUrl = process.argv[2]
let user = process.argv[3]
let password = process.argv[4]
let startTime = process.argv[5]
let defaultTimeout = 120*1000
let screenshotCreated = false
let loginPage = "/libs/granite/core/content/login.html"
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getCrxdeXPath(nodeName) {
return "//*[@id='repository']//a[span/text()='"+nodeName+"']"
}
async function waitForXPath(page, xpath) {
await page.waitForXPath(xpath, { timeout: defaultTimeout})
let nodeElements = await page.$x(xpath)
return nodeElements[0]
}
async function waitForCrxdeNodeWithName(page, nodeName) {
let nodeElXPath = getCrxdeXPath(nodeName)
return await waitForXPath(page, nodeElXPath)
}
async function waitForAndClick(page, selector, retry=true) {
await page.waitForSelector(selector, { timeout: defaultTimeout, visible: true})
if (await page.$(selector) === null) {
throw new Error("Could not find " + selector + " even after waiting for " + defaultTimeout);
}
try {
await page.click(selector)
} catch(err) {
if(retry) {
console.log("Error " +err+" while clicking on selector "+selector+", retrying...")
await sleep(200)
waitForAndClick(page, selector, false);
} else {
throw err
}
}
}
async function clickOnIconOfCrxdeNode(page, nodeHandle) {
await page.evaluate(node => node.previousSibling.previousSibling.click(), nodeHandle);
}
async function createScreenshot(page, name=null) {
if(page) {
await page.screenshot({path: 'run-'+startMillis+(name ? '-'+name : '')+'.png'});
screenshotCreated = true
}
}
async function run() {
var resultProps = {};
const browser = await puppeteer.launch({headless: true, dumpio: true, args: ['--window-size=1440,900', "--no-sandbox"]});
let page = null
try {
//const context = await browser.createIncognitoBrowserContext();
page = await browser.newPage(); // context or browser
await page.setViewport({width: 1440, height: 900});
await page.goto(baseUrl + loginPage);
await page.$eval('#username', (el, pUsername) => el.value = pUsername, user);
await page.$eval('#password', (el, pPassword) => el.value = pPassword, password);
await sleep(500);
await page.$eval('#login', form => form.submit());
await page.waitForNavigation({ waitUntil: 'networkidle0'})
if(new RegExp('.*?'+loginPage+'.*').test(await page.url())) {
throw new Error("Could not login!");
} else {
console.log("Logged in user "+user)
}
startMillis = new Date().getTime()
console.log("Starting user script at start milliseconds =", startMillis)
%SCRIPT%
} catch(err) {
console.log("Error during execution: ", err)
} finally {
if(!screenshotCreated) {
await createScreenshot(page)
}
browser.close();
let delta = new Date().getTime()-startMillis
console.log("----------------")
for (let key in resultProps) {
console.log(key+"="+resultProps[key])
}
console.log("browserDurationInMillis="+delta)
}
}
run();
'''.stripIndent()
}
def run(String scriptToRun) {
init()
new File(browserDir, 'test.js').text = testJsTemplate.replace('%SCRIPT%', scriptToRun)
def baseUrl = script.getBaseUrl()
def startTime = System.currentTimeMillis()
def cmd = "npm test $baseUrl $user $password $startTime"
def scriptResult = script.exec(cmd, browserDir, breakForDurationInSec)
new File(browserDir, 'run-'+new Date().getTime()+".log").text = scriptResult
if(scriptResult.contains("Error during execution:")) {
throw new IllegalStateException("Browser execution failed")
}
def propsString = StringUtils.substringAfter(scriptResult, "----------------\n")
resultProps = new Properties()
resultProps.load(new ByteArrayInputStream(propsString.getBytes(StandardCharsets.UTF_8)))
long browserDurationInMillis = resultProps.containsKey('breakForDurationInSec') ? Long.parseLong(resultProps.getProperty('breakForDurationInSec')) : (System.currentTimeMillis()-startTime)
return browserDurationInMillis
}
}
def logMsg(String msg) {
log.info(msg)
println(msg)
}
def getBaseUrl() {
def aemPort = System.getProperty('sun.java.command').replaceFirst(".*?-p ([0-9]+) .*", "\$1")
return "http://localhost:"+aemPort
}
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.apache.commons.exec.PumpStreamHandler
import org.apache.commons.exec.ExecuteWatchdog
def exec(String command, File dir, int breakForDurationInSec=600) {
CommandLine cmdLine = CommandLine.parse(command);
DefaultExecutor executor = new DefaultExecutor();
executor.setWorkingDirectory(dir)
ExecuteWatchdog watchdog = new ExecuteWatchdog(breakForDurationInSec * 1000);
executor.setWatchdog(watchdog);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream);
executor.setStreamHandler(streamHandler);
executor.setExitValues([0,1,143] as int[])
int exitValue = executor.execute(cmdLine);
String scriptOutput = outputStream.toString()
if(exitValue == 1) {
logMsg("Script '"+command+"' returned exit code 1, output:\n "+ scriptOutput)
}
return scriptOutput
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment