tapis_job-1.4.1-alpha1/src/Entity/TapisJob.php
src/Entity/TapisJob.php
<?php
namespace Drupal\tapis_job\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityChangedTrait;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\tapis_app\DrupalIds as AppDrupalIds;
use Drupal\tapis_job\TapisJobInterface;
use Drupal\tapis_system\DrupalIds as SystemDrupalIds;
use Drupal\tapis_tenant\DrupalIds as TenantDrupalIds;
use Drupal\user\EntityOwnerTrait;
/**
* Defines the job entity class.
*
* @ContentEntityType(
* id = "tapis_job",
* label = @Translation("Job"),
* label_collection = @Translation("Jobs"),
* label_singular = @Translation("job"),
* label_plural = @Translation("jobs"),
* label_count = @PluralTranslation(
* singular = "@count jobs",
* plural = "@count jobs",
* ),
* fieldable = TRUE,
* field_ui_base_route = "tapis_job.structure_settings",
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\tapis_job\TapisJobListBuilder",
* "views_data" = "Drupal\views\EntityViewsData",
* "access" = "Drupal\tapis_job\TapisJobAccessControlHandler",
* "form" = {
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* "route_provider" = {
* "html" = "Drupal\tapis_job\Entity\Routing\TapisJobRouteProvider",
* }
* },
* base_table = "tapis_job",
* admin_permission = "administer tapis job",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid",
* "owner" = "uid",
* },
* links = {
* "canonical" = "/tapis/job/{tapis_job}",
* "delete-form" = "/tapis/job/{tapis_job}/delete",
* },
* )
*/
class TapisJob extends ContentEntityBase implements TapisJobInterface {
use EntityChangedTrait;
use EntityOwnerTrait;
/**
* The Tapis job definition.
*
* @var array
*/
protected array $tapisDefinition;
/**
* The custom job resources.
*
* @var array
*/
protected array $customJobResources = [];
/**
* The custom properties.
*
* @var array
*/
protected array $customProperties = [];
/**
* The allocation ID.
*
* @var string|null
*/
protected ?string $allocationId = NULL;
/**
* The original job for restart.
*
* @var mixed|null
*/
protected $originalJobForRestart = NULL;
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['tapisId'] = BaseFieldDefinition::create('string')->setRequired(TRUE)->setLabel(t('Tapis ID'));
$fields['tapisUUID'] = BaseFieldDefinition::create('string')
->setRequired(TRUE)
->setLabel(t('Tapis UUID'))
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'textfield',
'weight' => 0,
]
)->setDisplayConfigurable('view', TRUE);
$fields['tenant'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Tenant'))
->setRequired(TRUE)
->setSetting('target_type', 'node')
->setSetting('handler', 'default')
->setSetting('handler_settings',
[
'target_bundles' => [TenantDrupalIds::NODE_BUNDLE_TENANT => TenantDrupalIds::NODE_BUNDLE_TENANT],
]
)
->setDisplayOptions('form',
[
'type' => 'entity_reference_autocomplete',
'settings' => [
'match_operator' => 'CONTAINS',
'size' => 60,
'placeholder' => '',
],
'weight' => 0,
]
)
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'entity_reference_label',
'weight' => 0,
]
)
->setDisplayConfigurable('view', TRUE);
$fields['app'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('App'))
->setRequired(TRUE)
->setSetting('target_type', 'node')
->setSetting('handler', 'default')
->setSetting('handler_settings',
[
'target_bundles' => [AppDrupalIds::NODE_BUNDLE_APP => AppDrupalIds::NODE_BUNDLE_APP],
]
)
->setDisplayOptions('form',
[
'type' => 'entity_reference_autocomplete',
'settings' => [
'match_operator' => 'CONTAINS',
'size' => 60,
'placeholder' => '',
],
'weight' => 0,
]
)
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'entity_reference_label',
'weight' => 0,
]
)
->setDisplayConfigurable('view', TRUE);
$fields['system'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('System'))
->setRequired(TRUE)
->setSetting('target_type', 'node')
->setSetting('handler', 'default')
->setSetting('handler_settings',
[
'target_bundles' => [SystemDrupalIds::NODE_BUNDLE_SYSTEM => SystemDrupalIds::NODE_BUNDLE_SYSTEM],
]
)
->setDisplayOptions('form',
[
'type' => 'options_select',
'weight' => 0,
]
)
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'entity_reference_label',
'weight' => 0,
]
)
->setDisplayConfigurable('view', TRUE);
$fields['webform_submission'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Webform submission'))
->setRequired(FALSE)
->setSetting('target_type', 'webform_submission')
->setSetting('handler', 'default')
->setDisplayConfigurable('form', FALSE)
->setDisplayConfigurable('view', FALSE);
$fields['proxyId'] = BaseFieldDefinition::create('string')
->setLabel(t('Proxy ID'))
->setRequired(FALSE)
->setSetting('max_length', 255);
$fields['remoteStarted'] = BaseFieldDefinition::create('datetime')
->setLabel(t('Job start timestamp'))
->setRequired(FALSE)
->setDisplayConfigurable('form', FALSE)
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'string',
]
)
->setDisplayConfigurable('view', TRUE);
$fields['remoteEnded'] = BaseFieldDefinition::create('datetime')
->setLabel(t('Job end timestamp'))
->setRequired(FALSE)
->setDisplayConfigurable('form', FALSE)
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'string',
]
)
->setDisplayConfigurable('view', TRUE);
$fields['tapisStatus'] = BaseFieldDefinition::create('string')
->setLabel(t('Status'))
->setRequired(FALSE)
->setSetting('max_length', 255)
->setDisplayConfigurable('form', FALSE)
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'string',
]
)
->setDisplayConfigurable('view', TRUE);
$fields['tapisCondition'] = BaseFieldDefinition::create('string')
->setLabel(t('Condition'))
->setRequired(FALSE)
->setSetting('max_length', 255)
->setDisplayConfigurable('form', FALSE)
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'string',
]
)
->setDisplayConfigurable('view', TRUE);
$fields['label'] = BaseFieldDefinition::create('string')
->setLabel(t('Label'))
->setRequired(TRUE)
->setSetting('max_length', 255)
->setDisplayOptions('form',
[
'type' => 'string_textfield',
'weight' => -5,
]
)
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view',
[
'label' => 'hidden',
'type' => 'string',
'weight' => -5,
]
)
->setDisplayConfigurable('view', TRUE);
$fields['description'] = BaseFieldDefinition::create('text_long')
->setLabel(t('Description'))
->setDisplayOptions('form',
[
'type' => 'text_textarea',
'weight' => 10,
]
)
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view',
[
'type' => 'text_default',
'label' => 'above',
'weight' => 10,
]
)
->setDisplayConfigurable('view', TRUE);
$fields['uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Owner'))
->setSetting('target_type', 'user')
->setDefaultValueCallback(static::class . '::getDefaultEntityOwner')
->setDisplayOptions('form',
[
'type' => 'entity_reference_autocomplete',
'settings' => [
'match_operator' => 'CONTAINS',
'size' => 60,
'placeholder' => '',
],
'weight' => 15,
]
)
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'author',
'weight' => 15,
]
)
->setDisplayConfigurable('view', TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Created'))
->setDescription(t('The time that the job was created.'))
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'timestamp',
'weight' => 20,
]
)
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('form',
[
'type' => 'datetime_timestamp',
'weight' => 20,
]
)
->setDisplayConfigurable('view', TRUE);
$fields['changed'] = BaseFieldDefinition::create('changed')->setLabel(t('Changed'))->setDescription(t('The time that the job was last edited.'));
$fields['remoteSubmitted'] = BaseFieldDefinition::create('datetime')
->setLabel(t('Job submit timestamp'))
->setRequired(FALSE)
->setDisplayConfigurable('form', FALSE)
->setDisplayOptions('view',
[
'label' => 'inline',
'type' => 'string',
]
)
->setDisplayConfigurable('view', TRUE);
$fields['jobUsage'] = BaseFieldDefinition::create('string_long')
->setLabel(t('Job Usage'))
->setDescription(t('Provide a summary of the job usage.'))
->setDefaultValue('')
->setRequired(FALSE)
->setDisplayOptions('view', [
'label' => 'inline',
'type' => 'basic_string',
])
->setDisplayConfigurable('view', TRUE)
->setDisplayConfigurable('form', FALSE);
return $fields;
}
/**
* {@inheritdoc}
*/
public static function alterJobView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
// Get details from Tapis API for this job.
/** @var \Drupal\tapis_job\TapisProvider\TapisJobProviderInterface */
$tapisJobProvider = \Drupal::service("tapis_job.tapis_job_provider");
/** @var \Drupal\tapis_job\TapisJobInterface $entity */
$jobOwnerId = $entity->getOwnerId();
$tenantId = $entity->getTenantId();
$tapisJob = $tapisJobProvider->getJob($tenantId, $entity->getTapisUUID(), $jobOwnerId);
$jobStatus = $tapisJob['status'];
$jobHistory = $tapisJobProvider->getJobHistory($tenantId, $entity->getTapisUUID(), $jobOwnerId);
$remoteStarted = NULL;
$remoteEnded = NULL;
// Iterate over the job history array.
foreach ($jobHistory as $jobHistoryItem) {
if ($jobHistoryItem['eventDetail'] == 'RUNNING') {
$remoteStarted = $jobHistoryItem['created'];
}
if ($jobHistoryItem['eventDetail'] == 'FINISHED' || $jobHistoryItem['eventDetail'] == 'CANCELLED' || $jobHistoryItem['eventDetail'] == 'FAILED') {
$remoteEnded = $jobHistoryItem['created'];
break;
}
}
if ($remoteEnded && !$remoteStarted) {
$remoteStarted = $remoteEnded;
}
// If ($remoteStarted) {
// $remoteStarted =
// substr($remoteStarted, 0, strpos($remoteStarted, '.')) . 'Z';
// dpm("Remote started: $remoteStarted");
// $remoteStarted = DrupalDateTime::createFromFormat(DateTime::RFC3339,
// $remoteStarted, 'UTC')->format(DATETIME_DATETIME_STORAGE_FORMAT);
// $entity->setRemoteStarted($remoteStarted);
// }
// If ($remoteEnded) {
// dpm("Remote ended: $remoteEnded");
// $remoteEnded = substr($remoteEnded, 0, strpos($remoteEnded, '.')) . 'Z';
// dpm("Remote started: $remoteStarted");
// $remoteEnded = DrupalDateTime::createFromFormat(DateTime::RFC3339,
// $remoteEnded, 'UTC')->format(DATETIME_DATETIME_STORAGE_FORMAT);;
// $entity->setRemoteEnded($remoteEnded);
// }
// Whenever we view the job, we update the status locally.
$entity->setStatus($jobStatus);
$entity->save();
$lowercaseJobStatus = strtolower($jobStatus);
$capitalizedJobStatus = str_replace('_', ' ', ucfirst($lowercaseJobStatus));
// Edit the build array's existing job status element value
// to be the new job status.
$build['tapisStatus'][0]['#context']['value'] = $capitalizedJobStatus;
$lastMessage = $tapisJob['lastMessage'];
$revisedLastMessage = str_replace('_', ' ', str_replace($jobStatus, $lowercaseJobStatus, $lastMessage));
$anAccessLinkForThisJob = $tapisJobProvider->getAJobAccessLinkForJob($entity);
if ($anAccessLinkForThisJob && $jobStatus === "RUNNING") {
// Only display a job access link for the job
// if it has a job access link and the job is running.
$jobProxyURLString = $anAccessLinkForThisJob->getProxyURL();
// Get a job.
$jobProxyURL = Url::fromUri($jobProxyURLString);
$link = new Link('Open app session', $jobProxyURL);
$build['tapis_job_proxyURL'] = $link->toRenderable();
$build['tapis_job_proxyURL']['#attributes'] = [
'class' => [
'button',
'button--primary',
],
'target' => '_blank',
];
$build[] = ['#type' => 'markup', '#markup' => '<br/>'];
}
$refreshJobStatuses =
[
"PENDING",
"PROCESSING_INPUTS",
"STAGING_INPUTS",
"STAGING_JOB",
"SUBMITTING_JOB",
"QUEUED",
"ARCHIVING",
];
if (in_array($jobStatus, $refreshJobStatuses)) {
$build['loader'] = [
'#type' => 'markup',
'#markup' => '<span class="loader"></span>',
];
}
// $build[] = ['#type' => 'markup', '#markup' => '<br/>'];
// $build['tapis_job_status'] = ['#type' => 'markup',
// '#markup' => "<div><b>Status</b> $jobStatus</div>",];
$build['tapis_job_lastMessage'] = [
'#type' => 'markup',
'#markup' => "<div><b>Last message</b> $revisedLastMessage</div>",
];
$build[] = ['#type' => 'markup', '#markup' => '<br/>'];
// Add the js library.
$build['#attached']['library'][] = 'tapis_job/tapis_job.jobpage';
$build['#attached']['drupalSettings']['tapis_job']['status'] = $jobStatus;
}
/**
* {@inheritdoc}
*/
public function getTenantId() {
return $this->get('tenant')->first()->getValue()['target_id'];
}
/**
* {@inheritdoc}
*/
public function getTapisUUID() {
return $this->get("tapisUUID")->value;
}
/**
* {@inheritdoc}
*/
public function setRemoteStarted(?string $remoteStarted): static {
$this->set('remoteStarted', $remoteStarted);
return $this;
}
/**
* {@inheritdoc}
*/
public function setRemoteEnded(?string $remoteEnded): static {
$this->set('remoteEnded', $remoteEnded);
return $this;
}
/**
* {@inheritdoc}
*/
public function setStatus(?string $status): static {
$this->set('tapisStatus', $status);
return $this;
}
/**
* {@inheritdoc}
*/
public function setCondition(?string $condition): static {
$this->set('tapisCondition', $condition);
return $this;
}
/**
* {@inheritdoc}
*/
public function setJobUsage(?string $jobUsage): static {
$this->set('jobUsage', $jobUsage);
return $this;
}
/**
* {@inheritdoc}
*/
public function setRemoteSubmitted(?string $remoteSubmitted): static {
$this->set('remoteSubmitted', $remoteSubmitted);
return $this;
}
/**
* {@inheritdoc}
*/
public function getRemoteStarted() {
return $this->get('remoteStarted')->value;
}
/**
* {@inheritdoc}
*/
public function getRemoteEnded() {
return $this->get('remoteEnded')->value;
}
/**
* {@inheritdoc}
*/
public function getStatus() {
return $this->get('tapisStatus')->value;
}
/**
* {@inheritdoc}
*/
public function getCondition() {
return $this->get('tapisCondition')->value;
}
/**
* {@inheritdoc}
*/
public function getJobUsage() {
return $this->get('jobUsage')->value;
}
/**
* {@inheritdoc}
*/
public function getRemoteSubmitted() {
return $this->get('remoteSubmitted')->value;
}
/**
* {@inheritdoc}
*/
public function getTapisId() {
return $this->get("tapisId")->value;
}
/**
* {@inheritdoc}
*/
public function setTapisId(?string $jobId) {
$this->set('tapisId', $jobId);
}
/**
* {@inheritdoc}
*/
public function setTapisUUID(?string $jobUuid): void {
$this->set('tapisUUID', $jobUuid);
}
/**
* {@inheritdoc}
*/
public function setProxyId($proxyId): void {
$this->set('proxyId', $proxyId);
}
/**
* {@inheritdoc}
*/
public function getTapisDefinition(): array {
return $this->tapisDefinition;
}
/**
* {@inheritdoc}
*/
public function setTapisDefinition(?array $definition): void {
$this->tapisDefinition = $definition;
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage): void {
parent::preSave($storage);
if (!$this->getOwnerId()) {
// If no owner has been set explicitly, make the anonymous user the owner.
$this->setOwnerId(0);
}
}
/**
* {@inheritdoc}
*/
public function getWebformSubmissionId() {
$webformSubmission = $this->get('webform_submission')->first();
if ($webformSubmission !== null) {
return $this->get('webform_submission')->first()->getValue()['target_id'];
} else {
return null;
}
}
/**
* {@inheritdoc}
*/
public function getSystemId() {
return $this->get('system')->first()->getValue()['target_id'];
}
/**
* {@inheritdoc}
*/
public function getAppId() {
return $this->get('app')->first()->getValue()['target_id'];
}
/**
* {@inheritdoc}
*/
public function setCustomJobResources($customJobResources): void {
$this->customJobResources = $customJobResources;
}
/**
* {@inheritdoc}
*/
public function setCustomProperties(array $properties): void {
$this->customProperties = $properties;
}
/**
* {@inheritdoc}
*/
public function setAllocationId($allocation_id): void {
$this->allocationId = $allocation_id;
}
/**
* {@inheritdoc}
*/
public function setOriginalJobForRestart($originalJob): void {
$this->originalJobForRestart = $originalJob;
}
/**
* {@inheritdoc}
*/
public function toJSON($appArgsString = "", $schedulerOptions = [], $envVars = [], $jobFileInputArrays = []) {
\Drupal::logger('tapis_job')->debug("Creating JSON for job");
$tenant = $this->getTenant();
$app = $this->getApp();
$system = $this->get("system")->first();
if ($system) {
/** @var \Drupal\Core\Entity\Plugin\DataType\EntityReference $systemEntity */
$systemEntity = $system->get("entity");
$system = $systemEntity->getTarget()->getValue();
}
$tapisSystemProvider = \Drupal::service("tapis_system.tapis_system_provider");
$tenantId = $system->get(SystemDrupalIds::SYSTEM_TENANT)->first()->getValue()['target_id'];
$tapisSystemId = $system->get(SystemDrupalIds::SYSTEM_TAPIS_ID)->first()->getValue()['value'];
$doesSystemUseStaticSystemUser = boolval($system->get(SystemDrupalIds::SYSTEM_USE_STATIC_SYSTEM_USER)->getValue()[0]['value']);
\Drupal::logger('tapis_job')->debug("System uses static system user: $doesSystemUseStaticSystemUser");
$systemOwnerUid = $system->getOwnerId();
$tapisSystemJson = $tapisSystemProvider->getSystem($tenantId, $tapisSystemId, $systemOwnerUid);
$tapisSystemJobWorkingDir = $tapisSystemJson['jobWorkingDir'];
\Drupal::logger('tapis_job')->debug("Job working dir: $tapisSystemJobWorkingDir");
$json = [];
$modifiedJobName = str_replace(' ', '_', $this->label());
$json['name'] = $modifiedJobName;
if ($this->originalJobForRestart) {
// Copy the execSystemExecDir, execSystemInputDir,
// and execSystemOutputDir from the original job.
$tapisJobProvider = \Drupal::service("tapis_job.tapis_job_provider");
$originalTapisJob = $tapisJobProvider->getJob($this->originalJobForRestart->getTenantId(), $this->originalJobForRestart->getTapisUUID(), $this->originalJobForRestart->getOwnerId());
$json['execSystemExecDir'] = $originalTapisJob['execSystemExecDir'];
$json['execSystemInputDir'] = $originalTapisJob['execSystemInputDir'];
$json['execSystemOutputDir'] = $originalTapisJob['execSystemOutputDir'];
}
elseif (strpos($tapisSystemJobWorkingDir, '${JobOwner}') !== FALSE) {
$tapisTokenProvider = \Drupal::service("tapis_auth.tapis_token_provider");
$tapisUsername = $tapisTokenProvider->getTapisUsername($tenantId, $this->getOwnerId());
// Replace ${JobOwner} with the current user's tapis username
// due to validation we run in the system node form,
// we know that tapisSystemJobWorkingDir
// must end with ${JobOwner} or ${JobOwner}/
// for all systems that use a static system user.
$tapisSystemJobWorkingDir = str_replace('${JobOwner}', $tapisUsername, $tapisSystemJobWorkingDir);
// Also check if the job working directory has
// the ${EffectiveUserId} token in it,
// then replace it with the current user's system credential account name
// to do that, we first check if this system uses a static system user.
if ($doesSystemUseStaticSystemUser) {
// System has a static effective user,
// so we use the static username to replace ${EffectiveUserId} with.
$system_static_username = $system->get(SystemDrupalIds::SYSTEM_EFFECTIVE_USER)->getValue()[0]['value'];
$tapisSystemJobWorkingDir = str_replace('${EffectiveUserId}', $system_static_username, $tapisSystemJobWorkingDir);
}
else {
// System has a dynamic effective user,
// so the current user must have their own credential,
// and that's the value we use to replace ${EffectiveUserId} with.
if (strpos($tapisSystemJobWorkingDir, '${EffectiveUserId}') !== FALSE) {
// Get the system credential to use for this user and system,
// get its loginUser value, and replace [loginUser] with it.
$systemCredentialIds = \Drupal::entityQuery("tapis_system_credential")
->condition("uid", \Drupal::currentUser()->id())
->condition("system", $system->id())
->accessCheck(FALSE)
->execute();
// Check if $tapisSystemJobWorkingDir has [loginUser] in it.
$systemCredentialId = reset($systemCredentialIds);
// Load the tapis_system_credential entity.
/** @var \Drupal\tapis_system\Entity\TapisSystemCredential $systemCredential */
$systemCredential = \Drupal::entityTypeManager()->getStorage('tapis_system_credential')->load($systemCredentialId);
// Get the login user field.
$loginUser = $systemCredential->get("loginUser")->getValue()[0]['value'];
// Replace ${JobOwner} with the current user's tapis username
// due to validation we run in the system node form,
// we know that tapisSystemJobWorkingDir
// must end with ${JobOwner} or ${JobOwner}/.
$tapisSystemJobWorkingDir = str_replace('${EffectiveUserId}', $loginUser, $tapisSystemJobWorkingDir);
}
}
$tapisSystemJobWorkingDir = '/' . trim($tapisSystemJobWorkingDir, '/') . '/';
\Drupal::logger('tapis_job')->debug("Granting MODIFY permission to $tapisUsername on $tapisSystemJobWorkingDir");
// Grant the current user modify permissions on the user-specific path.
$tapisSystemProvider->grantSystemPermission($tenantId, $tapisSystemId, $tapisUsername, "MODIFY", $tapisSystemJobWorkingDir, $systemOwnerUid);
}
elseif (!$doesSystemUseStaticSystemUser) {
$tapisTokenProvider = \Drupal::service("tapis_auth.tapis_token_provider");
$tapisUsername = $tapisTokenProvider->getTapisUsername($tenantId, $this->getOwnerId());
// If the job working directory has the ${EffectiveUserId} token in it,
// then replace it with the current user's system credential account name.
if (strpos($tapisSystemJobWorkingDir, '${EffectiveUserId}') !== FALSE) {
// Get the system credential to use for this user and system,
// get its loginUser value, and replace [loginUser] with it.
$systemCredentialIds = \Drupal::entityQuery("tapis_system_credential")
->condition("uid", \Drupal::currentUser()->id())
->condition("system", $system->id())
->accessCheck(FALSE)
->execute();
// Check if $tapisSystemJobWorkingDir has ${EffectiveUserId} in it.
$systemCredentialId = reset($systemCredentialIds);
// Load the tapis_system_credential entity.
/** @var \Drupal\tapis_system\Entity\TapisSystemCredential $systemCredential */
$systemCredential = \Drupal::entityTypeManager()->getStorage('tapis_system_credential')->load($systemCredentialId);
// Get the login user field.
$loginUser = $systemCredential->get("loginUser")->getValue()[0]['value'];
// Replace ${JobOwner} with the current user's tapis username
// due to validation we run in the system node form,
// we know that tapisSystemJobWorkingDir
// must end with ${JobOwner} or ${JobOwner}/.
$tapisSystemJobWorkingDir = str_replace('${EffectiveUserId}', $loginUser, $tapisSystemJobWorkingDir);
}
// also, check if the job working directory
// has the ${JobOwner} token in it,
// then replace it with the current user's tapis username.
if (strpos($tapisSystemJobWorkingDir, '${JobOwner}') !== FALSE) {
// Replace ${JobOwner} with the current user's tapis username
// due to validation we run in the system node form,
// we know that tapisSystemJobWorkingDir must end
// with ${JobOwner} or ${JobOwner}/.
$tapisSystemJobWorkingDir = str_replace('${JobOwner}', $tapisUsername, $tapisSystemJobWorkingDir);
}
$tapisSystemJobWorkingDir = '/' . trim($tapisSystemJobWorkingDir, '/') . '/';
\Drupal::logger('tapis_job')->debug("Granting MODIFY permission to $tapisUsername on $tapisSystemJobWorkingDir");
// Grant the current user modify permissions on the user-specific path.
$tapisSystemProvider->grantSystemPermission($tenantId, $tapisSystemId, $tapisUsername, "MODIFY", $tapisSystemJobWorkingDir, $systemOwnerUid);
}
if (!$this->originalJobForRestart) {
// If this is not a restart, then we need to set the execSystemExecDir,
// execSystemInputDir, and execSystemOutputDir
// set the execSystemExecDir, execSystemInputDir, and execSystemOutputDir.
// ExecDir is where the job will be executed
// InputDir is where the job will read input files from
// OutputDir is where the job will write output files to
// Later, when you want to use Job name
// as the folder name, instead of Job UUID,
// yo would replace ${JobUUID} with ${JobName}
// (or better yet, the macro that Tapis will hopefully soon have
// that will be job name + an auto-incrementing number
// at the end to avoid name collisions)
// $json['execSystemExecDir'] = '${JobWorkingDir}/jobs/${JobUUID}';
// $json['execSystemInputDir'] = '${JobWorkingDir}/jobs/${JobUUID}';
// $json['execSystemOutputDir'] =
// '${JobWorkingDir}/jobs/${JobUUID}/outputs';.
$json['execSystemExecDir'] = '${JobWorkingDir}/jobs/${JobName}';
$json['execSystemInputDir'] = '${JobWorkingDir}/jobs/${JobName}';
$json['execSystemOutputDir'] = '${JobWorkingDir}/jobs/${JobName}/outputs';
if (!$app->get(AppDrupalIds::APP_JOB_WORKING_DIR_PREFIX)->isEmpty()) {
$appJobWoringDirPrefix = $app->get(AppDrupalIds::APP_JOB_WORKING_DIR_PREFIX)
->getValue()[0]['value'];
if ($appJobWoringDirPrefix) {
$appJobWoringDirPrefix = trim($appJobWoringDirPrefix);
\Drupal::logger("tapis_job")
->debug("Application specific job working directory prefix = $appJobWoringDirPrefix");
// Check if the last character is not a hyphen
if (substr($appJobWoringDirPrefix, -1) !== '-') {
// Add a hyphen to the end of the string
$appJobWoringDirPrefix .= '-';
}
$appJobWoringDirPrefix .= '${JobUUID}';
$json['execSystemExecDir'] = '${JobWorkingDir}/jobs/' . $appJobWoringDirPrefix;
$json['execSystemInputDir'] = '${JobWorkingDir}/jobs/' . $appJobWoringDirPrefix;
$json['execSystemOutputDir'] = '${JobWorkingDir}/jobs/' . $appJobWoringDirPrefix . '/outputs';
}
}
}
$appId = $app->get(AppDrupalIds::APP_TAPIS_ID)->getValue()[0]['value'];
$appVersion = $app->get(AppDrupalIds::APP_VERSION)->getValue()[0]['value'];
$json['appId'] = $appId;
$json['appVersion'] = $appVersion;
$json['execSystemId'] = $system->get(SystemDrupalIds::SYSTEM_TAPIS_ID)->getValue()[0]['value'];
$json['parameterSet'] = [
'envVariables' => [],
'containerArgs' => [],
'appArgs' => [],
'schedulerOptions' => [],
];
// Add all the environment variables.
foreach ($envVars as $envVar) {
// Split the envVar into name and value.
$envVar = explode("=", $envVar);
$json['parameterSet']['envVariables'][] = [
'key' => $envVar[0],
'value' => $envVar[1] ?? "",
];
}
$appInputType = $app->get(AppDrupalIds::APP_INPUT_TYPE)->getValue()[0]['value'];
if ($appInputType === "flexible_parameters" && !empty($appArgsString)) {
$json['parameterSet']['appArgs'][] = [
'name' => 'fixed_command',
'arg' => $appArgsString,
];
}
if (!$app->get(AppDrupalIds::APP_CONTAINER_ARGS)->isEmpty()) {
$containerArgsString = $app->get(AppDrupalIds::APP_CONTAINER_ARGS)->getValue()[0]['value'];
if ($containerArgsString) {
$containerArgsString = trim($containerArgsString);
/*
* containerArgsString will be a string of the form:
* key1 value1
* key2 value2
* ...
* where key is the name of the container argument and value
* is the value of the container argument.
* Each line will be a separate container argument,
* and the value can be empty.
* When a value is present, it will be separated from the key
* by the first space character (' ').
* Otherwise, the line will just be the key
* (and if a space character is present at the end, it will be ignored).
*
* Note: the value may contain a space character, but the key cannot.
*/
// Parse the containerArgsString into an array of container arguments.
$containerArgLineIndex = 0;
foreach (explode("\n", $containerArgsString) as $containerArgLine) {
$containerArgLine = trim($containerArgLine);
// Use the Drupal token service to replace tokens
// in the container argument (include current user in context)
$containerArgLine = \Drupal::token()->replace($containerArgLine,
[
'tapis_job' => $this,
'user' => \Drupal::currentUser(),
]
);
$json['parameterSet']['containerArgs'][] = [
'name' => "container_arg_$containerArgLineIndex",
"arg" => $containerArgLine,
];
$containerArgLineIndex++;
}
}
}
for ($i = 0; $i < count($schedulerOptions); $i++) {
$json['parameterSet']['schedulerOptions'][] = ["arg" => $schedulerOptions[$i]];
}
if ($this->allocationId) {
$json['parameterSet']['schedulerOptions'][] = ["arg" => "--account " . trim($this->allocationId)];
}
$appType = $app->get(AppDrupalIds::APP_TYPE)->getValue()[0]['value'];
$app_runtime = $app->get(AppDrupalIds::APP_RUNTIME)->getValue()[0]['value'];
if ($appType === "web" || $appType === "vnc" || $app_runtime === "EXECUTABLE") {
$is_executable_app = ($app_runtime === "EXECUTABLE") ? "1" : "0";
$setup_app_proxying = ($appType === "web" || $appType === "vnc") ? "1" : "0";
$satellite_proxy_url = $tenant->get(TenantDrupalIds::TENANT_SATELLITE_PROXY_URL)->getValue()[0]['uri'];
// Remove the http:// and https:// schemes from the url.
$satellite_proxy_url = preg_replace('/^https?:\/\//', '', $satellite_proxy_url);
$proxyId = "0";
if ($appType === "web" || $appType === "vnc") {
$proxyId = $this->getProxyId();
}
$launcher_path = $system->get(SystemDrupalIds::SYSTEM_LAUNCHER_PATH)->getValue()[0]['value'];
if ($launcher_path) {
// Trim.
$launcher_path = trim($launcher_path);
}
$json['cmdPrefix'] = "$launcher_path $is_executable_app $setup_app_proxying $satellite_proxy_url $proxyId ";
}
// Handle custom job resources (if allowed by this app)
$user_can_override_num_nodes = boolval($app->get(AppDrupalIds::OVERRIDE_NUM_NODES)->getValue()[0]['value']);
$user_can_override_cores_per_node = boolval($app->get(AppDrupalIds::OVERRIDE_CORES_PER_NODE)->getValue()[0]['value']);
$user_can_override_memory = boolval($app->get(AppDrupalIds::OVERRIDE_MEMORY)->getValue()[0]['value']);
$user_can_override_max_runtime = boolval($app->get(AppDrupalIds::OVERRIDE_MAX_RUNTIME)->getValue()[0]['value']);
if ($user_can_override_num_nodes && array_key_exists(AppDrupalIds::APP_NUM_NODES, $this->customJobResources)) {
$json['nodeCount'] = intval($this->customJobResources[AppDrupalIds::APP_NUM_NODES]);
}
if ($user_can_override_cores_per_node && array_key_exists(AppDrupalIds::APP_CORES_PER_NODE, $this->customJobResources)) {
$json['coresPerNode'] = intval($this->customJobResources[AppDrupalIds::APP_CORES_PER_NODE]);
}
if ($user_can_override_memory && array_key_exists(AppDrupalIds::APP_MEMORY_MB, $this->customJobResources)) {
$json['memoryMB'] = intval($this->customJobResources[AppDrupalIds::APP_MEMORY_MB]);
}
if ($user_can_override_max_runtime && array_key_exists(AppDrupalIds::APP_MAX_MINUTES, $this->customJobResources)) {
$json['maxMinutes'] = intval($this->customJobResources[AppDrupalIds::APP_MAX_MINUTES]);
}
// Check if the $jobFileInputArrays is not empty.
if (!empty($jobFileInputArrays)) {
$json['fileInputArrays'] = $jobFileInputArrays;
}
// Merge in custom properties.
if ($this->customProperties) {
$json = array_merge($json, $this->customProperties);
}
return $json;
}
/**
* {@inheritdoc}
*/
public function getTenant() {
$tenant = $this->get("tenant")->first();
// Return $tenant?->get("entity")->getTarget()->getValue();
if (!empty($tenant)) {
/** @var \Drupal\Core\Entity\Plugin\DataType\EntityReference $tenantEntity */
$tenantEntity = $tenant->get("entity");
return $tenantEntity->getTarget()->getValue();
}
else {
return NULL;
}
}
/**
* {@inheritdoc}
*/
public function getApp() {
$app = $this->get("app")->first();
// $app_target = $app?->get("entity")->getTarget();
if (!empty($app)) {
/** @var \Drupal\Core\Entity\Plugin\DataType\EntityReference $appEntity */
$appEntity = $app->get("entity");
$app_target = $appEntity->getTarget();
}
else {
$app_target = NULL;
}
if (!$app_target) {
\Drupal::messenger()->addError("The app for this job id could not be found: " . $this->id());
return NULL;
}
return $app_target->getValue();
}
/**
* {@inheritdoc}
*/
public function getProxyId() {
return $this->get("proxyId")->value;
}
/**
* {@inheritdoc}
*/
public function getSystem() {
$system = $this->get("system")->first();
// Return $system?->get("entity")->getTarget()->getValue();
if (!empty($system)) {
/** @var \Drupal\Core\Entity\Plugin\DataType\EntityReference $systemEntity */
$systemEntity = $system->get("entity");
if ($systemEntity->getTarget()) {
return $systemEntity->getTarget()->getValue();
}
else {
return NULL;
}
}
else {
return NULL;
}
}
}
