Skip to content

Commit

Permalink
Merge branch 'develop' into persistenceManager
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryanseanlee committed Sep 17, 2024
2 parents a81fdbf + d293341 commit ad8e3f2
Show file tree
Hide file tree
Showing 21 changed files with 465 additions and 260 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,33 @@

## 22.0-SNAPSHOT

### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW)
* All `Timer`, `Cache`, and `CachedValue` object require a 'name' property. This property was
previously optional in many cases, but is now required in order to support new cluster features,
logging, and admin tools. The new `BaseService.resources` property now will give access to all
resources by name, if needed and replaces `BaseService.timers`.

* `BaseService` methods `getIMap()`, `getReplicatedMap()` and `getISet()` have been changed to
`createIMap()`, `createReplicatedMap()` and `createISet()`, respectively. This change provides
a consistent interface for all resources on BaseService and is not expected to impact most
applications.

### 🎁 New Features
* `Cache` and `CachedValue` should now be created using a factory on `BaseService`. This streamlined
interface reduces boilerplate, and provides a consistent interface with `Timer`.

### ⚙️ Technical

* Improvements to `Timer` to avoid extra executions when primary instance changes.

* Updated `ClusterService` to use Hoist's `InstanceNotFoundException` class to designate routine.

* Exposed `/xh/ping` as whitelisted route for basic uptime/reachability checks. Retained legacy
`/ping` alias, but prefer this new path going forward.

* Improvements to `RestController` to better support editing Domain Objects defined with secondary
domain objects.

## 21.0.1 - 2024-09-05

### 🐞 Bug Fixes
Expand Down
138 changes: 74 additions & 64 deletions grails-app/controllers/io/xh/hoist/RestController.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@

package io.xh.hoist

import grails.gorm.transactions.Transactional
import grails.validation.ValidationException
import io.xh.hoist.json.JSONParser

@Transactional
abstract class RestController extends BaseController {

def trackService
Expand All @@ -20,92 +18,104 @@ abstract class RestController extends BaseController {
static restTarget = null // Implementations set to value of GORM domain class they are editing.

def create() {
def data = parseRequestJSON().data
preprocessSubmit(data)
restTargetVal.withTransaction {
def data = parseRequestJSON().data
preprocessSubmit(data)

def obj = restTargetVal.newInstance(data)
doCreate(obj, data)
noteChange(obj, 'CREATE')
renderJSON(success:true, data:obj)
def obj = restTargetVal.newInstance(data)
doCreate(obj, data)
noteChange(obj, 'CREATE')
renderJSON(success:true, data:obj)
}
}

def read() {
def query = params.query ? JSONParser.parseObject(params.query) : [:],
ret = params.id ? [restTargetVal.get(params.id)] : doList(query)
renderJSON(success:true, data:ret)
restTargetVal.withTransaction {
def query = params.query ? JSONParser.parseObject(params.query) : [:],
ret = params.id ? [restTargetVal.get(params.id)] : doList(query)
renderJSON(success:true, data:ret)
}
}

def update() {
def data = parseRequestJSON().data
preprocessSubmit(data)
restTargetVal.withTransaction {
def data = parseRequestJSON().data
preprocessSubmit(data)

def obj = restTargetVal.get(data.id)
try {
doUpdate(obj, data)
noteChange(obj, 'UPDATE')
renderJSON(success:true, data:obj)
} catch (ValidationException ex) {
obj.discard()
throw ex
def obj = restTargetVal.get(data.id)
try {
doUpdate(obj, data)
noteChange(obj, 'UPDATE')
renderJSON(success:true, data:obj)
} catch (ValidationException ex) {
obj.discard()
throw ex
}
}
}

def bulkUpdate() {
def body = parseRequestJSON(),
ids = body.ids,
newParams = body.newParams,
successCount = 0,
failCount = 0,
target = restTargetVal,
obj = null

ids.each { id ->
try {
obj = target.get(id)
doUpdate(obj, newParams)
noteChange(obj, 'UPDATE')
successCount++
} catch (Exception e) {
failCount++
if (e instanceof ValidationException) {
e = new io.xh.hoist.exception.ValidationException(e)
logDebug("Validation exception updating ${obj}", e)
} else {
logError("Unexpected exception updating ${obj}", e)
restTargetVal.withTransaction {
def body = parseRequestJSON(),
ids = body.ids,
newParams = body.newParams,
successCount = 0,
failCount = 0,
target = restTargetVal,
obj = null

ids.each { id ->
try {
obj = target.get(id)
doUpdate(obj, newParams)
noteChange(obj, 'UPDATE')
successCount++
} catch (Exception e) {
failCount++
if (e instanceof ValidationException) {
e = new io.xh.hoist.exception.ValidationException(e)
logDebug("Validation exception updating ${obj}", e)
} else {
logError("Unexpected exception updating ${obj}", e)
}
}
}
}

renderJSON(success:successCount, fail:failCount)
renderJSON(success:successCount, fail:failCount)
}
}

def delete() {
def obj = restTargetVal.get(params.id)
doDelete(obj)
noteChange(obj, 'DELETE')
renderJSON(success:true)
restTargetVal.withTransaction {
def obj = restTargetVal.get(params.id)
doDelete(obj)
noteChange(obj, 'DELETE')
renderJSON(success:true)
}
}

def bulkDelete() {
def ids = params.list('ids'),
successCount = 0,
failCount = 0,
target = restTargetVal

ids.each {id ->
try {
target.withTransaction{ status ->
def obj = target.get(id)
doDelete(obj)
noteChange(obj, 'DELETE')
successCount++
restTargetVal.withTransaction {
def ids = params.list('ids'),
successCount = 0,
failCount = 0,
target = restTargetVal

ids.each {id ->
try {
target.withTransaction{ status ->
def obj = target.get(id)
doDelete(obj)
noteChange(obj, 'DELETE')
successCount++
}
} catch (Exception ignored) {
failCount++
}
} catch (Exception ignored) {
failCount++
}
}

renderJSON(success:successCount, fail:failCount)
renderJSON(success:successCount, fail:failCount)
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@

package io.xh.hoist.admin

import grails.gorm.transactions.Transactional
import io.xh.hoist.RestController
import io.xh.hoist.security.Access

@Transactional
@Access(['HOIST_ADMIN_READER'])
abstract class AdminRestController extends RestController{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.xh.hoist.BaseController
import io.xh.hoist.cluster.ClusterRequest
import io.xh.hoist.configuration.LogbackConfig
import io.xh.hoist.security.Access
import io.xh.hoist.exception.RoutineRuntimeException

import static io.xh.hoist.util.Utils.getAppContext

Expand Down Expand Up @@ -65,15 +66,12 @@ class LogViewerAdminController extends BaseController {
Boolean caseSensitive

def doCall() {
if (!availableFiles[filename]) throwUnavailable()

// Catch any exceptions and render clean failure - the admin client auto-polls for log file
// updates, and we don't want to spam the logs with a repeated stacktrace.
if (!availableFiles[filename]) throwUnavailable(filename)
try {
def content = appContext.logReaderService.readFile(filename, startLine, maxLines, pattern, caseSensitive)
return [success: true, filename: filename, content: content]
} catch (Exception e) {
return [success: false, filename: filename, content: [], exception: e.message]
} catch (FileNotFoundException ignored) {
throwUnavailable(filename)
}
}
}
Expand All @@ -99,7 +97,7 @@ class LogViewerAdminController extends BaseController {
String filename

File doCall() {
if (!availableFiles[filename]) throwUnavailable()
if (!availableFiles[filename]) throwUnavailable(filename)
return appContext.logReaderService.get(filename)
}
}
Expand All @@ -122,7 +120,7 @@ class LogViewerAdminController extends BaseController {

filenames.each { filename ->
def toDelete = available[filename]
if (!toDelete) throwUnavailable()
if (!toDelete) throwUnavailable(filename)

def deleted = toDelete.delete()
if (!deleted) logWarn("Failed to delete log: '$filename'.")
Expand Down Expand Up @@ -167,7 +165,7 @@ class LogViewerAdminController extends BaseController {
}
}

static void throwUnavailable() {
throw new RuntimeException('Filename not valid or available')
static void throwUnavailable(String filename) {
throw new RoutineRuntimeException("Filename not valid or available: $filename")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ class ConnectionPoolMonitoringService extends BaseService {

void init() {
createTimer(
interval: {enabled ? config.snapshotInterval * DateTimeUtils.SECONDS: -1},
runFn: this.&takeSnapshot
name: 'takeSnapshot',
runFn: this.&takeSnapshot,
interval: {enabled ? config.snapshotInterval * DateTimeUtils.SECONDS: -1}
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ class MemoryMonitoringService extends BaseService {

void init() {
createTimer(
interval: {this.enabled ? config.snapshotInterval * DateTimeUtils.SECONDS: -1},
runFn: this.&takeSnapshot
name: 'takeSnapshot',
runFn: this.&takeSnapshot,
interval: {this.enabled ? config.snapshotInterval * DateTimeUtils.SECONDS: -1}
)
}

Expand Down Expand Up @@ -178,6 +179,6 @@ class MemoryMonitoringService extends BaseService {

Map getAdminStats() {[
config: configForAdminStats('xhMemoryMonitoringConfig'),
latestSnapshot: latestSnapshot,
latestSnapshot: latestSnapshot
]}
}
40 changes: 20 additions & 20 deletions grails-app/services/io/xh/hoist/admin/ServiceManagerService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package io.xh.hoist.admin

import com.hazelcast.core.DistributedObject
import io.xh.hoist.BaseService

class ServiceManagerService extends BaseService {
Expand All @@ -15,8 +16,6 @@ class ServiceManagerService extends BaseService {
clusterAdminService

Collection<Map> listServices() {


getServicesInternal().collect { name, svc ->
return [
name: name,
Expand All @@ -28,24 +27,8 @@ class ServiceManagerService extends BaseService {

Map getStats(String name) {
def svc = grailsApplication.mainContext.getBean(name),
prefix = svc.class.name + '_',
timers = svc.timers*.adminStats,
distObjs = clusterService.distributedObjects
.findAll { it.getName().startsWith(prefix) }
.collect {clusterAdminService.getAdminStatsForObject(it)}

Map ret = svc.adminStats
if (timers || distObjs) {
ret = ret.clone()
if (distObjs) ret.distributedObjects = distObjs
if (timers.size() == 1) {
ret.timer = timers[0]
} else if (timers.size() > 1) {
ret.timers = timers
}
}

return ret
resources = getResourceStats(svc)
return resources ? [*: svc.adminStats, resources: resources] : svc.adminStats
}

void clearCaches(List<String> names) {
Expand All @@ -60,6 +43,23 @@ class ServiceManagerService extends BaseService {
}
}

//----------------------
// Implementation
//----------------------
private List getResourceStats(BaseService svc) {
svc.resources
.findAll { !it.key.startsWith('xh_') } // skip hoist implementation objects
.collect { k, v ->
Map stats = v instanceof DistributedObject ?
clusterAdminService.getAdminStatsForObject(v) :
v.adminStats

// rely on the name (key) service knows, i.e avoid HZ prefix
return [*: stats, name: k]
}
}


private Map<String, BaseService> getServicesInternal() {
return grailsApplication.mainContext.getBeansOfType(BaseService.class, false, false)
}
Expand Down
Loading

0 comments on commit ad8e3f2

Please sign in to comment.