Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F96151692
DrydockBlueprint.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Mon, Dec 23, 04:54
Size
12 KB
Mime Type
text/x-php
Expires
Wed, Dec 25, 04:54 (2 d)
Engine
blob
Format
Raw Data
Handle
23133957
Attached To
rPH Phabricator
DrydockBlueprint.php
View Options
<?php
/**
* @task lease Lease Acquisition
* @task resource Resource Allocation
* @task log Logging
*/
abstract
class
DrydockBlueprint
{
private
$activeResource
;
private
$activeLease
;
abstract
public
function
getType
();
abstract
public
function
getInterface
(
DrydockResource
$resource
,
DrydockLease
$lease
,
$type
);
abstract
public
function
isEnabled
();
public
function
getBlueprintClass
()
{
return
get_class
(
$this
);
}
protected
function
loadLease
(
$lease_id
)
{
$query
=
id
(
new
DrydockLeaseQuery
())
->
withIDs
(
array
(
$lease_id
))
->
needResources
(
true
)
->
execute
();
$lease
=
idx
(
$query
,
$lease_id
);
if
(!
$lease
)
{
throw
new
Exception
(
"No such lease '{$lease_id}'!"
);
}
return
$lease
;
}
/* -( Lease Acquisition )-------------------------------------------------- */
/**
* @task lease
*/
final
public
function
filterResource
(
DrydockResource
$resource
,
DrydockLease
$lease
)
{
$scope
=
$this
->
pushActiveScope
(
$resource
,
$lease
);
return
$this
->
canAllocateLease
(
$resource
,
$lease
);
}
/**
* Enforce basic checks on lease/resource compatibility. Allows resources to
* reject leases if they are incompatible, even if the resource types match.
*
* For example, if a resource represents a 32-bit host, this method might
* reject leases that need a 64-bit host. If a resource represents a working
* copy of repository "X", this method might reject leases which need a
* working copy of repository "Y". Generally, although the main types of
* a lease and resource may match (e.g., both "host"), it may not actually be
* possible to satisfy the lease with a specific resource.
*
* This method generally should not enforce limits or perform capacity
* checks. Perform those in @{method:shouldAllocateLease} instead. It also
* should not perform actual acquisition of the lease; perform that in
* @{method:executeAcquireLease} instead.
*
* @param DrydockResource Candidiate resource to allocate the lease on.
* @param DrydockLease Pending lease that wants to allocate here.
* @return bool True if the resource and lease are compatible.
* @task lease
*/
abstract
protected
function
canAllocateLease
(
DrydockResource
$resource
,
DrydockLease
$lease
);
/**
* @task lease
*/
final
public
function
allocateLease
(
DrydockResource
$resource
,
DrydockLease
$lease
)
{
$scope
=
$this
->
pushActiveScope
(
$resource
,
$lease
);
$this
->
log
(
'Trying to Allocate Lease'
);
$lease
->
setStatus
(
DrydockLeaseStatus
::
STATUS_ACQUIRING
);
$lease
->
setResourceID
(
$resource
->
getID
());
$lease
->
attachResource
(
$resource
);
$ephemeral_lease
=
id
(
clone
$lease
)->
makeEphemeral
();
$allocated
=
false
;
$allocation_exception
=
null
;
$resource
->
openTransaction
();
$resource
->
beginReadLocking
();
$resource
->
reload
();
$other_leases
=
id
(
new
DrydockLease
())->
loadAllWhere
(
'status IN (%Ld) AND resourceID = %d'
,
array
(
DrydockLeaseStatus
::
STATUS_ACQUIRING
,
DrydockLeaseStatus
::
STATUS_ACTIVE
,
),
$resource
->
getID
());
try
{
$allocated
=
$this
->
shouldAllocateLease
(
$resource
,
$ephemeral_lease
,
$other_leases
);
}
catch
(
Exception
$ex
)
{
$allocation_exception
=
$ex
;
}
if
(
$allocated
)
{
$lease
->
save
();
}
$resource
->
endReadLocking
();
if
(
$allocated
)
{
$resource
->
saveTransaction
();
$this
->
log
(
'Allocated Lease'
);
}
else
{
$resource
->
killTransaction
();
$this
->
log
(
'Failed to Allocate Lease'
);
}
if
(
$allocation_exception
)
{
$this
->
logException
(
$allocation_exception
);
}
return
$allocated
;
}
/**
* Enforce lease limits on resources. Allows resources to reject leases if
* they would become over-allocated by accepting them.
*
* For example, if a resource represents disk space, this method might check
* how much space the lease is asking for (say, 200MB) and how much space is
* left unallocated on the resource. It could grant the lease (return true)
* if it has enough remaining space (more than 200MB), and reject the lease
* (return false) if it does not (less than 200MB).
*
* A resource might also allow only exclusive leases. In this case it could
* accept a new lease (return true) if there are no active leases, or reject
* the new lease (return false) if there any other leases.
*
* A lock is held on the resource while this method executes to prevent
* multiple processes from allocating leases on the resource simultaneously.
* However, this means you should implement the method as cheaply as possible.
* In particular, do not perform any actual acquisition or setup in this
* method.
*
* If allocation is permitted, the lease will be moved to `ACQUIRING` status
* and @{method:executeAcquireLease} will be called to actually perform
* acquisition.
*
* General compatibility checks unrelated to resource limits and capacity are
* better implemented in @{method:canAllocateLease}, which serves as a
* cheap filter before lock acquisition.
*
* @param DrydockResource Candidate resource to allocate the lease on.
* @param DrydockLease Pending lease that wants to allocate here.
* @param list<DrydockLease> Other allocated and acquired leases on the
* resource. The implementation can inspect them
* to verify it can safely add the new lease.
* @return bool True to allocate the lease on the resource;
* false to reject it.
* @task lease
*/
abstract
protected
function
shouldAllocateLease
(
DrydockResource
$resource
,
DrydockLease
$lease
,
array
$other_leases
);
/**
* @task lease
*/
final
public
function
acquireLease
(
DrydockResource
$resource
,
DrydockLease
$lease
)
{
$scope
=
$this
->
pushActiveScope
(
$resource
,
$lease
);
$this
->
log
(
'Acquiring Lease'
);
$lease
->
setStatus
(
DrydockLeaseStatus
::
STATUS_ACTIVE
);
$lease
->
setResourceID
(
$resource
->
getID
());
$lease
->
attachResource
(
$resource
);
$ephemeral_lease
=
id
(
clone
$lease
)->
makeEphemeral
();
try
{
$this
->
executeAcquireLease
(
$resource
,
$ephemeral_lease
);
}
catch
(
Exception
$ex
)
{
$this
->
logException
(
$ex
);
throw
$ex
;
}
$lease
->
setAttributes
(
$ephemeral_lease
->
getAttributes
());
$lease
->
save
();
$this
->
log
(
'Acquired Lease'
);
}
/**
* Acquire and activate an allocated lease. Allows resources to peform setup
* as leases are brought online.
*
* Following a successful call to @{method:canAllocateLease}, a lease is moved
* to `ACQUIRING` status and this method is called after resource locks are
* released. Nothing is locked while this method executes; the implementation
* is free to perform expensive operations like writing files and directories,
* executing commands, etc.
*
* After this method executes, the lease status is moved to `ACTIVE` and the
* original leasee may access it.
*
* If acquisition fails, throw an exception.
*
* @param DrydockResource Resource to acquire a lease on.
* @param DrydockLease Lease to acquire.
* @return void
*/
abstract
protected
function
executeAcquireLease
(
DrydockResource
$resource
,
DrydockLease
$lease
);
final
public
function
releaseLease
(
DrydockResource
$resource
,
DrydockLease
$lease
)
{
$scope
=
$this
->
pushActiveScope
(
null
,
$lease
);
$released
=
false
;
$lease
->
openTransaction
();
$lease
->
beginReadLocking
();
$lease
->
reload
();
if
(
$lease
->
getStatus
()
==
DrydockLeaseStatus
::
STATUS_ACTIVE
)
{
$lease
->
setStatus
(
DrydockLeaseStatus
::
STATUS_RELEASED
);
$lease
->
save
();
$released
=
true
;
}
$lease
->
endReadLocking
();
$lease
->
saveTransaction
();
if
(!
$released
)
{
throw
new
Exception
(
"Unable to release lease: lease not active!"
);
}
}
/* -( Resource Allocation )------------------------------------------------ */
public
function
canAllocateMoreResources
(
array
$pool
)
{
return
true
;
}
abstract
protected
function
executeAllocateResource
(
DrydockLease
$lease
);
final
public
function
allocateResource
(
DrydockLease
$lease
)
{
$scope
=
$this
->
pushActiveScope
(
null
,
$lease
);
$this
->
log
(
pht
(
"Blueprint '%s': Allocating Resource for '%s'"
,
$this
->
getBlueprintClass
(),
$lease
->
getLeaseName
()));
try
{
$resource
=
$this
->
executeAllocateResource
(
$lease
);
$this
->
validateAllocatedResource
(
$resource
);
}
catch
(
Exception
$ex
)
{
$this
->
logException
(
$ex
);
throw
$ex
;
}
return
$resource
;
}
/* -( Logging )------------------------------------------------------------ */
/**
* @task log
*/
protected
function
logException
(
Exception
$ex
)
{
$this
->
log
(
$ex
->
getMessage
());
}
/**
* @task log
*/
protected
function
log
(
$message
)
{
self
::
writeLog
(
$this
->
activeResource
,
$this
->
activeLease
,
$message
);
}
/**
* @task log
*/
public
static
function
writeLog
(
DrydockResource
$resource
=
null
,
DrydockLease
$lease
=
null
,
$message
)
{
$log
=
id
(
new
DrydockLog
())
->
setEpoch
(
time
())
->
setMessage
(
$message
);
if
(
$resource
)
{
$log
->
setResourceID
(
$resource
->
getID
());
}
if
(
$lease
)
{
$log
->
setLeaseID
(
$lease
->
getID
());
}
$log
->
save
();
}
public
static
function
getAllBlueprints
()
{
static
$list
=
null
;
if
(
$list
===
null
)
{
$blueprints
=
id
(
new
PhutilSymbolLoader
())
->
setType
(
'class'
)
->
setAncestorClass
(
'DrydockBlueprint'
)
->
setConcreteOnly
(
true
)
->
selectAndLoadSymbols
();
$list
=
ipull
(
$blueprints
,
'name'
,
'name'
);
foreach
(
$list
as
$class_name
=>
$ignored
)
{
$list
[
$class_name
]
=
newv
(
$class_name
,
array
());
}
}
return
$list
;
}
public
static
function
getAllBlueprintsForResource
(
$type
)
{
static
$groups
=
null
;
if
(
$groups
===
null
)
{
$groups
=
mgroup
(
self
::
getAllBlueprints
(),
'getType'
);
}
return
idx
(
$groups
,
$type
,
array
());
}
protected
function
newResourceTemplate
(
$name
)
{
$resource
=
new
DrydockResource
();
$resource
->
setBlueprintClass
(
$this
->
getBlueprintClass
());
$resource
->
setType
(
$this
->
getType
());
$resource
->
setStatus
(
DrydockResourceStatus
::
STATUS_PENDING
);
$resource
->
setName
(
$name
);
$resource
->
save
();
$this
->
activeResource
=
$resource
;
$this
->
log
(
pht
(
"Blueprint '%s': Created New Template"
,
$this
->
getBlueprintClass
()));
return
$resource
;
}
/**
* Sanity checks that the blueprint is implemented properly.
*/
private
function
validateAllocatedResource
(
$resource
)
{
$blueprint
=
$this
->
getBlueprintClass
();
if
(!(
$resource
instanceof
DrydockResource
))
{
throw
new
Exception
(
"Blueprint '{$blueprint}' is not properly implemented: "
.
"executeAllocateResource() must return an object of type "
.
"DrydockResource or throw, but returned something else."
);
}
$current_status
=
$resource
->
getStatus
();
$req_status
=
DrydockResourceStatus
::
STATUS_OPEN
;
if
(
$current_status
!=
$req_status
)
{
$current_name
=
DrydockResourceStatus
::
getNameForStatus
(
$current_status
);
$req_name
=
DrydockResourceStatus
::
getNameForStatus
(
$req_status
);
throw
new
Exception
(
"Blueprint '{$blueprint}' is not properly implemented: "
.
"executeAllocateResource() must return a DrydockResource with "
.
"status '{$req_name}', but returned one with status "
.
"'{$current_name}'."
);
}
}
private
function
pushActiveScope
(
DrydockResource
$resource
=
null
,
DrydockLease
$lease
=
null
)
{
if
((
$this
->
activeResource
!==
null
)
||
(
$this
->
activeLease
!==
null
))
{
throw
new
Exception
(
"There is already an active resource or lease!"
);
}
$this
->
activeResource
=
$resource
;
$this
->
activeLease
=
$lease
;
return
new
DrydockBlueprintScopeGuard
(
$this
);
}
public
function
popActiveScope
()
{
$this
->
activeResource
=
null
;
$this
->
activeLease
=
null
;
}
}
Event Timeline
Log In to Comment