Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14238627
D14583.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
70 KB
Referenced Files
None
Subscribers
None
D14583.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -850,6 +850,30 @@
'DrydockDefaultViewCapability' => 'applications/drydock/capability/DrydockDefaultViewCapability.php',
'DrydockFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockFilesystemInterface.php',
'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php',
+ 'DrydockKVMAllocatedImageLogType' => 'applications/drydock/logtype/DrydockKVMAllocatedImageLogType.php',
+ 'DrydockKVMAllocatingImageLogType' => 'applications/drydock/logtype/DrydockKVMAllocatingImageLogType.php',
+ 'DrydockKVMAssignedIPAddressLogType' => 'applications/drydock/logtype/DrydockKVMAssignedIPAddressLogType.php',
+ 'DrydockKVMAttemptConnectionLogType' => 'applications/drydock/logtype/DrydockKVMAttemptConnectionLogType.php',
+ 'DrydockKVMCreatedVMLogType' => 'applications/drydock/logtype/DrydockKVMCreatedVMLogType.php',
+ 'DrydockKVMCreatingVMLogType' => 'applications/drydock/logtype/DrydockKVMCreatingVMLogType.php',
+ 'DrydockKVMCredentialUsageLogType' => 'applications/drydock/logtype/DrydockKVMCredentialUsageLogType.php',
+ 'DrydockKVMFailedConnectionLogType' => 'applications/drydock/logtype/DrydockKVMFailedConnectionLogType.php',
+ 'DrydockKVMHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockKVMHostBlueprintImplementation.php',
+ 'DrydockKVMRemovedImageLogType' => 'applications/drydock/logtype/DrydockKVMRemovedImageLogType.php',
+ 'DrydockKVMRemovingImageLogType' => 'applications/drydock/logtype/DrydockKVMRemovingImageLogType.php',
+ 'DrydockKVMRetrievedImagePathLogType' => 'applications/drydock/logtype/DrydockKVMRetrievedImagePathLogType.php',
+ 'DrydockKVMRetrievedMACAddressLogType' => 'applications/drydock/logtype/DrydockKVMRetrievedMACAddressLogType.php',
+ 'DrydockKVMRetrievingImagePathLogType' => 'applications/drydock/logtype/DrydockKVMRetrievingImagePathLogType.php',
+ 'DrydockKVMRetrievingMACAddressLogType' => 'applications/drydock/logtype/DrydockKVMRetrievingMACAddressLogType.php',
+ 'DrydockKVMShutdownVMLogType' => 'applications/drydock/logtype/DrydockKVMShutdownVMLogType.php',
+ 'DrydockKVMShuttingDownVMLogType' => 'applications/drydock/logtype/DrydockKVMShuttingDownVMLogType.php',
+ 'DrydockKVMSuccessfulConnectionLogType' => 'applications/drydock/logtype/DrydockKVMSuccessfulConnectionLogType.php',
+ 'DrydockKVMUncleanShutdownLogType' => 'applications/drydock/logtype/DrydockKVMUncleanShutdownLogType.php',
+ 'DrydockKVMUnexpectedVMShutdownLogType' => 'applications/drydock/logtype/DrydockKVMUnexpectedVMShutdownLogType.php',
+ 'DrydockKVMVirtualMachineShutdownException' => 'applications/drydock/exception/DrydockKVMVirtualMachineShutdownException.php',
+ 'DrydockKVMWaitingForConnectivityLogType' => 'applications/drydock/logtype/DrydockKVMWaitingForConnectivityLogType.php',
+ 'DrydockKVMWaitingForIPAddressLogType' => 'applications/drydock/logtype/DrydockKVMWaitingForIPAddressLogType.php',
+ 'DrydockKVMWinRMCredentialUsageLogType' => 'applications/drydock/logtype/DrydockKVMWinRMCredentialUsageLogType.php',
'DrydockLandRepositoryOperation' => 'applications/drydock/operation/DrydockLandRepositoryOperation.php',
'DrydockLease' => 'applications/drydock/storage/DrydockLease.php',
'DrydockLeaseAcquiredLogType' => 'applications/drydock/logtype/DrydockLeaseAcquiredLogType.php',
@@ -3715,6 +3739,7 @@
'ReleephWorkRecordConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php',
'ReleephWorkRecordPickStatusConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php',
'RemarkupProcessConduitAPIMethod' => 'applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php',
+ 'RemoteTempFile' => 'applications/drydock/interface/command/RemoteTempFile.php',
'RepositoryConduitAPIMethod' => 'applications/repository/conduit/RepositoryConduitAPIMethod.php',
'RepositoryCreateConduitAPIMethod' => 'applications/repository/conduit/RepositoryCreateConduitAPIMethod.php',
'RepositoryQueryConduitAPIMethod' => 'applications/repository/conduit/RepositoryQueryConduitAPIMethod.php',
@@ -4707,6 +4732,30 @@
'DrydockDefaultViewCapability' => 'PhabricatorPolicyCapability',
'DrydockFilesystemInterface' => 'DrydockInterface',
'DrydockInterface' => 'Phobject',
+ 'DrydockKVMAllocatedImageLogType' => 'DrydockLogType',
+ 'DrydockKVMAllocatingImageLogType' => 'DrydockLogType',
+ 'DrydockKVMAssignedIPAddressLogType' => 'DrydockLogType',
+ 'DrydockKVMAttemptConnectionLogType' => 'DrydockLogType',
+ 'DrydockKVMCreatedVMLogType' => 'DrydockLogType',
+ 'DrydockKVMCreatingVMLogType' => 'DrydockLogType',
+ 'DrydockKVMCredentialUsageLogType' => 'DrydockLogType',
+ 'DrydockKVMFailedConnectionLogType' => 'DrydockLogType',
+ 'DrydockKVMHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
+ 'DrydockKVMRemovedImageLogType' => 'DrydockLogType',
+ 'DrydockKVMRemovingImageLogType' => 'DrydockLogType',
+ 'DrydockKVMRetrievedImagePathLogType' => 'DrydockLogType',
+ 'DrydockKVMRetrievedMACAddressLogType' => 'DrydockLogType',
+ 'DrydockKVMRetrievingImagePathLogType' => 'DrydockLogType',
+ 'DrydockKVMRetrievingMACAddressLogType' => 'DrydockLogType',
+ 'DrydockKVMShutdownVMLogType' => 'DrydockLogType',
+ 'DrydockKVMShuttingDownVMLogType' => 'DrydockLogType',
+ 'DrydockKVMSuccessfulConnectionLogType' => 'DrydockLogType',
+ 'DrydockKVMUncleanShutdownLogType' => 'DrydockLogType',
+ 'DrydockKVMUnexpectedVMShutdownLogType' => 'DrydockLogType',
+ 'DrydockKVMVirtualMachineShutdownException' => 'PhabricatorWorkerYieldException',
+ 'DrydockKVMWaitingForConnectivityLogType' => 'DrydockLogType',
+ 'DrydockKVMWaitingForIPAddressLogType' => 'DrydockLogType',
+ 'DrydockKVMWinRMCredentialUsageLogType' => 'DrydockLogType',
'DrydockLandRepositoryOperation' => 'DrydockRepositoryOperationType',
'DrydockLease' => array(
'DrydockDAO',
@@ -8138,6 +8187,7 @@
'ReleephWorkRecordConduitAPIMethod' => 'ReleephConduitAPIMethod',
'ReleephWorkRecordPickStatusConduitAPIMethod' => 'ReleephConduitAPIMethod',
'RemarkupProcessConduitAPIMethod' => 'ConduitAPIMethod',
+ 'RemoteTempFile' => 'Phobject',
'RepositoryConduitAPIMethod' => 'ConduitAPIMethod',
'RepositoryCreateConduitAPIMethod' => 'RepositoryConduitAPIMethod',
'RepositoryQueryConduitAPIMethod' => 'RepositoryConduitAPIMethod',
diff --git a/src/applications/drydock/blueprint/DrydockKVMHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockKVMHostBlueprintImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/blueprint/DrydockKVMHostBlueprintImplementation.php
@@ -0,0 +1,909 @@
+<?php
+
+final class DrydockKVMHostBlueprintImplementation
+ extends DrydockBlueprintImplementation {
+
+ const RESOURCE_STATE_HOST_NOT_ALLOCATED = 'host-not-allocated';
+ const RESOURCE_STATE_WAIT_FOR_IP_ADDRESS = 'wait-for-ip-address';
+ const RESOURCE_STATE_TEST_CONNECTIVITY = 'test-connectivity';
+ const RESOURCE_STATE_READY = 'ready';
+
+ const LEASE_STATE_CHECK_VM_ONLINE = 'check-vm-online';
+ const LEASE_STATE_WAIT_FOR_IP_ADDRESS = 'wait-for-ip-address';
+ const LEASE_STATE_READY = 'ready';
+
+ public function isEnabled() {
+ return true;
+ }
+
+ public function getBlueprintName() {
+ return pht('KVM Hosts');
+ }
+
+ public function getDescription() {
+ return pht(
+ 'Allows Drydock to build and lease virtual machine hosts '.
+ 'on a machine with KVM installed.');
+ }
+
+ public function canAnyBlueprintEverAllocateResourceForLease(
+ DrydockLease $lease) {
+ return true;
+ }
+
+ public function canEverAllocateResourceForLease(
+ DrydockBlueprint $blueprint,
+ DrydockLease $lease) {
+
+ return true;
+ }
+
+ public function canAllocateResourceForLease(
+ DrydockBlueprint $blueprint,
+ DrydockLease $lease) {
+
+ return true;
+ }
+
+
+ public function allocateResource(
+ DrydockBlueprint $blueprint,
+ DrydockLease $lease) {
+
+ $resource = $this->newResourceTemplate($blueprint)
+ ->setAttribute('state', self::RESOURCE_STATE_HOST_NOT_ALLOCATED)
+ ->needSlotLock('kvm.'.$blueprint->getPHID());
+
+ return $resource->allocateResource();
+ }
+
+ public function activateResource(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource) {
+
+ switch ($resource->getAttribute('state')) {
+ case self::RESOURCE_STATE_HOST_NOT_ALLOCATED:
+ $this->performHostAllocation($blueprint, $resource);
+ break;
+ case self::RESOURCE_STATE_WAIT_FOR_IP_ADDRESS:
+ $this->checkIfIPAddressIsAssigned($blueprint, $resource);
+ break;
+ case self::RESOURCE_STATE_TEST_CONNECTIVITY:
+ $this->testConnectivity($blueprint, $resource);
+ break;
+ case self::RESOURCE_STATE_READY:
+ return $resource->activateResource();
+ default:
+ throw new Exception(pht(
+ 'Resource is in an unexpected KVM state: "%s".',
+ $resource->getAttribute('state')));
+ }
+
+ }
+
+ public function destroyResource(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource) {
+
+ if (!$resource->getAttribute('vm-name') ||
+ !$resource->getAttribute('image-name') ||
+ !$resource->getAttribute('storage-pool')) {
+ return;
+ }
+
+ try {
+ $loaded_credential = PassphraseSSHKey::loadFromPHID(
+ $blueprint->getFieldValue('keypair'),
+ PhabricatorUser::getOmnipotentUser());
+
+ $resource->logEvent(
+ DrydockKVMShutdownVMLogType::LOGCONST,
+ array(
+ 'vm-name' => $resource->getAttribute('vm-name'),
+ ));
+
+ $future = $this->getSSHFuture(
+ $blueprint,
+ $loaded_credential,
+ '/usr/bin/virsh destroy %s',
+ $resource->getAttribute('vm-name'));
+ $future->resolvex();
+
+ $resource->logEvent(
+ DrydockKVMShutdownVMLogType::LOGCONST,
+ array(
+ 'vm-name' => $resource->getAttribute('vm-name'),
+ ));
+
+ $resource->logEvent(
+ DrydockKVMRemovingImageLogType::LOGCONST,
+ array(
+ 'image-name' => $resource->getAttribute('image-name'),
+ ));
+
+ $future = $this->getSSHFuture(
+ $blueprint,
+ $loaded_credential,
+ '/usr/bin/virsh vol-delete %s --pool %s',
+ $resource->getAttribute('image-name'),
+ $resource->getAttribute('storage-pool'));
+ $future->resolvex();
+
+ $resource->logEvent(
+ DrydockKVMRemovedImageLogType::LOGCONST,
+ array(
+ 'image-name' => $resource->getAttribute('image-name'),
+ ));
+ } catch (CommandException $ex) {
+ $resource->logEvent(
+ DrydockKVMUncleanShutdownLogType::LOGCONST,
+ array(
+ 'vm-name' => $resource->getAttribute('vm-name'),
+ 'image-name' => $resource->getAttribute('image-name'),
+ 'stdout' => $ex->getStdout(),
+ 'stderr' => $ex->getStderr(),
+ ));
+ }
+
+ return;
+ }
+
+ public function getResourceName(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource) {
+ $vm_name = $resource->getAttribute(
+ 'vm-name',
+ pht('<Unknown>'));
+ return pht('Host (%s)', $vm_name);
+ }
+
+ public function canAcquireLeaseOnResource(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
+ DrydockLease $lease) {
+
+ return true;
+ }
+
+ public function acquireLease(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
+ DrydockLease $lease) {
+
+ $lease
+ ->setAttribute('state', self::LEASE_STATE_CHECK_VM_ONLINE)
+ ->acquireOnResource($resource);
+ }
+
+ public function activateLease(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
+ DrydockLease $lease) {
+
+ if ($resource->isActive()) {
+ try {
+ switch ($lease->getAttribute('state')) {
+ case self::LEASE_STATE_CHECK_VM_ONLINE:
+ $this->checkVMOnline($blueprint, $resource, $lease);
+ break;
+ case self::LEASE_STATE_WAIT_FOR_IP_ADDRESS:
+ $this->checkIfIPAddressIsAssigned($blueprint, $resource, $lease);
+ break;
+ case self::LEASE_STATE_READY:
+ $lease->activateOnResource($resource);
+ break;
+ default:
+ throw new Exception(pht(
+ 'Lease is in an unexpected KVM state: "%s".',
+ $lease->getAttribute('state')));
+ }
+ } catch (DrydockKVMVirtualMachineShutdownException $ex) {
+ $lease->logEvent(
+ DrydockKVMUnexpectedVMShutdownLogType::LOGCONST,
+ array(
+ 'vm-name' => $resource->getAttribute('vm-name'),
+ ));
+
+ $resource->setStatus(DrydockResourceStatus::STATUS_BROKEN);
+ $resource->save();
+ $resource->scheduleUpdate();
+
+ throw new Exception('Virtual machine no longer exists or is shut down');
+ }
+ }
+
+ throw new PhabricatorWorkerYieldException(1);
+ }
+
+ public function checkVMOnline(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
+ DrydockLease $lease) {
+
+ $loaded_credential = PassphraseSSHKey::loadFromPHID(
+ $blueprint->getFieldValue('keypair'),
+ PhabricatorUser::getOmnipotentUser());
+
+ $future = $this->getSSHFuture(
+ $blueprint,
+ $loaded_credential,
+ '/usr/bin/virsh domstate %s',
+ $resource->getAttribute('vm-name'));
+ list($err, $stdout, $stderr) = $future->resolve();
+ $status = trim($stdout).trim($stderr);
+ if (substr_count($status, 'shut off') > 0 ||
+ substr_count($status, 'Domain not found') > 0) {
+ throw new DrydockKVMVirtualMachineShutdownException();
+ }
+
+ $lease->setAttribute('state', self::LEASE_STATE_WAIT_FOR_IP_ADDRESS);
+ $lease->save();
+ }
+
+ public function didReleaseLease(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
+ DrydockLease $lease) {
+ // Almanac hosts stick around indefinitely so we don't need to recycle them
+ // if they don't have any leases.
+ return;
+ }
+
+ public function destroyLease(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
+ DrydockLease $lease) {
+ // We don't create anything when activating a lease, so we don't need to
+ // throw anything away.
+ return;
+ }
+
+ public function getType() {
+ return 'host';
+ }
+
+ public function getInterface(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
+ DrydockLease $lease,
+ $type) {
+
+ $viewer = PhabricatorUser::getOmnipotentUser();
+
+ switch ($type) {
+ case DrydockCommandInterface::INTERFACE_TYPE:
+ return $this->getCommandInterfaceWithExplicitHost(
+ $blueprint,
+ $resource,
+ $lease->getAttribute('ip-address'))
+ ->pushWorkingDirectory($lease->getAttribute('path'));
+ break;
+ }
+ }
+
+ private function getCommandInterfaceWithExplicitHost(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource,
+ $host) {
+
+ $loaded_credential = PassphraseSSHKey::loadFromPHID(
+ $blueprint->getFieldValue('keypair'),
+ PhabricatorUser::getOmnipotentUser());
+
+ return id(new DrydockSSHCommandInterface())
+ ->setConfig('host', $host)
+ ->setConfig('port', $resource->getAttribute('port'))
+ ->setConfig('credentialPHID', $resource->getAttribute('keypair'))
+ ->setConfig('platform', $resource->getAttribute('platform'))
+ ->setSSHProxy(
+ $blueprint->getFieldValue('host'),
+ $blueprint->getFieldValue('port'),
+ $loaded_credential);
+ }
+
+ public function getFieldSpecifications() {
+ return array(
+ 'kvm' => array(
+ 'name' => pht('KVM Host Configuration'),
+ 'type' => 'header',
+ ),
+ 'host' => array(
+ 'name' => pht('SSH Host'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht('e.g. 10.0.0.1'),
+ ),
+ 'port' => array(
+ 'name' => pht('SSH Port'),
+ 'type' => 'text',
+ 'required' => false,
+ 'caption' => pht('Defaults to port 22'),
+ ),
+ 'keypair' => array(
+ 'name' => pht('SSH Key Pair'),
+ 'type' => 'credential',
+ 'required' => true,
+ 'credential.provides'
+ => PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE,
+ 'caption' => pht(
+ 'Used to connect over SSH to the KVM host.'),
+ ),
+ 'winrm-auth' => array(
+ 'name' => pht('WinRM Credentials'),
+ 'type' => 'credential',
+ 'credential.provides'
+ => PassphrasePasswordCredentialType::PROVIDES_TYPE,
+ 'caption' => pht(
+ 'This is only required if the platform is "windows".'),
+ ),
+ 'platform' => array(
+ 'name' => pht('Platform Name'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht('e.g. %s or %s', 'windows', 'linux'),
+ ),
+ 'storage-path' => array(
+ 'name' => pht('Storage Path'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht(
+ 'A writable location on the instance where new directories / files '.
+ 'can be created and data can be stored in.'),
+ ),
+ 'machine' => array(
+ 'name' => pht('Instance Configuration'),
+ 'type' => 'header',
+ ),
+ 'cpu' => array(
+ 'name' => pht('CPUs'),
+ 'type' => 'int',
+ 'required' => true,
+ 'caption' => pht('The number of CPUs to allocate to instances.'),
+ ),
+ 'ram' => array(
+ 'name' => pht('RAM'),
+ 'type' => 'int',
+ 'required' => true,
+ 'caption' => pht(
+ 'The amount of RAM (in megabytes) to '.
+ 'allocate to instances.'),
+ ),
+ 'storage-pool' => array(
+ 'name' => pht('Storage Pool'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht(
+ 'The storage pool to clone instance images into.'),
+ ),
+ 'base-image' => array(
+ 'name' => pht('Base Image Name'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht(
+ 'The name of the qcow2 image in the storage pool, which '.
+ 'is cloned for new instances.'),
+ ),
+ 'network' => array(
+ 'name' => pht('Networking Configuration'),
+ 'type' => 'header',
+ ),
+ 'network-id' => array(
+ 'name' => pht('Network Name'),
+ 'type' => 'text',
+ 'caption' => pht(
+ 'The name of the libvirt network to assign to the instance.'),
+ ),
+ 'dnsmasq-path' => array(
+ 'name' => pht('DNSMasq Leases Path'),
+ 'type' => 'text',
+ 'caption' => pht(
+ 'The path to the DNSMasq leases file, usually located at a path '.
+ 'such as /var/lib/libvirt/dnsmasq/<network>.leases.'),
+ ),
+ 'dnsmasq-is-json' => array(
+ 'name' => pht('DNSMasq Leases are JSON'),
+ 'type' => 'bool',
+ 'caption' => pht(
+ 'Whether or not the lease file is stored in a JSON format.'),
+ ),
+ 'attr-header' => array(
+ 'name' => pht('Host Attributes'),
+ 'type' => 'header',
+ ),
+ 'attributes' => array(
+ 'name' => pht('Host Attributes'),
+ 'type' => 'textarea',
+ 'caption' => pht(
+ 'A newline separated list of host attributes. Each attribute '.
+ 'should be specified in a key=value format.'),
+ 'monospace' => true,
+ ),
+ ) + parent::getFieldSpecifications();
+ }
+
+
+ // ------------------------------------------------------------------
+
+
+ private function performHostAllocation(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource) {
+
+ $credential = id(new PassphraseCredentialQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs(array($blueprint->getFieldValue('keypair')))
+ ->executeOne();
+
+ if ($credential === null) {
+ throw new Exception('Specified credential does not exist!');
+ }
+
+ $resource->logEvent(
+ DrydockKVMCredentialUsageLogType::LOGCONST,
+ array(
+ 'phid' => $credential->getPHID(),
+ ));
+
+ $loaded_credential = PassphraseSSHKey::loadFromPHID(
+ $blueprint->getFieldValue('keypair'),
+ PhabricatorUser::getOmnipotentUser());
+
+ $winrm_auth_id = null;
+ if ($blueprint->getFieldValue('platform') === 'windows') {
+ $winrm_auth = id(new PassphraseCredentialQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs(array($blueprint->getFieldValue('winrm-auth')))
+ ->executeOne();
+
+ if ($winrm_auth === null) {
+ throw new Exception(
+ 'Specified credential for WinRM auth does not exist!');
+ }
+
+ $winrm_auth_id = $winrm_auth->getID();
+
+ $resource->logEvent(
+ DrydockKVMWinRMCredentialUsageLogType::LOGCONST,
+ array(
+ 'phid' => $winrm_auth->getPHID(),
+ ));
+ }
+
+ $pool_name = $blueprint->getFieldValue('storage-pool');
+ $image_name = 'image-'.$resource->getID();
+ $vm_name = 'vm-'.$resource->getID();
+
+ $resource->logEvent(
+ DrydockKVMAllocatingImageLogType::LOGCONST,
+ array(
+ 'base-image' => $blueprint->getFieldValue('base-image'),
+ 'new-image' => $image_name,
+ ));
+
+ $future = $this->getSSHFuture(
+ $blueprint,
+ $loaded_credential,
+ '/usr/bin/virsh vol-create-as %s %s 256GB '.
+ '--format qcow2 --backing-vol %s '.
+ '--backing-vol-format qcow2',
+ $blueprint->getFieldValue('storage-pool'),
+ $image_name,
+ $blueprint->getFieldValue('base-image'));
+ $future->resolvex();
+
+ $resource->logEvent(
+ DrydockKVMAllocatedImageLogType::LOGCONST,
+ array(
+ 'base-image' => $blueprint->getFieldValue('base-image'),
+ 'new-image' => $image_name,
+ ));
+
+ $resource->logEvent(
+ DrydockKVMRetrievingImagePathLogType::LOGCONST,
+ array(
+ 'new-image' => $image_name,
+ ));
+
+ $future = $this->getSSHFuture(
+ $blueprint,
+ $loaded_credential,
+ '/usr/bin/virsh vol-path %s --pool %s',
+ $image_name,
+ $blueprint->getFieldValue('storage-pool'));
+ list($stdout, $stderr) = $future->resolvex();
+ $image_path = trim($stdout);
+
+ $resource->logEvent(
+ DrydockKVMRetrievedImagePathLogType::LOGCONST,
+ array(
+ 'new-image' => $image_name,
+ 'path' => $image_path,
+ ));
+
+ $ram = $blueprint->getFieldValue('ram');
+ $vcpu = $blueprint->getFieldValue('cpu');
+ $network = $blueprint->getFieldValue('network-id');
+
+ $xml = <<<EOF
+<domain type='kvm'>
+ <name>$vm_name</name>
+ <memory unit='MiB'>$ram</memory>
+ <currentMemory unit='MiB'>$ram</currentMemory>
+ <vcpu placement='static'>$vcpu</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc-1.1'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <features>
+ <acpi/>
+ <apic/>
+ <pae/>
+ </features>
+ <cpu mode='custom' match='exact'>
+ <model fallback='allow'>Nehalem</model>
+ </cpu>
+ <clock offset='localtime'>
+ <timer name='rtc' tickpolicy='catchup'/>
+ <timer name='pit' tickpolicy='delay'/>
+ <timer name='hpet' present='no'/>
+ </clock>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>restart</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
+ <disk type='file' device='disk'>
+ <driver name='qemu' type='qcow2' cache='writeback'/>
+ <source file='$image_path' />
+ <target dev='vda' bus='virtio'/>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x0b' function='0x0'/>
+ </disk>
+ <controller type='usb' index='0' model='ich9-ehci1'>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x05' function='0x0'/>
+ </controller>
+ <controller type='usb' index='0' model='ich9-uhci1'>
+ <master startport='0'/>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x06' function='0x0'/>
+ </controller>
+ <controller type='usb' index='0' model='ich9-uhci2'>
+ <master startport='2'/>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x07' function='0x0'/>
+ </controller>
+ <controller type='usb' index='0' model='ich9-uhci3'>
+ <master startport='4'/>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x08' function='0x0'/>
+ </controller>
+ <controller type='ide' index='0'>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x01' function='0x1'/>
+ </controller>
+ <controller type='virtio-serial' index='0'>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x09' function='0x0'/>
+ </controller>
+ <interface type='network'>
+ <source network='$network'/>
+ <model type='virtio'/>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x03' function='0x0'/>
+ </interface>
+ <serial type='pty'>
+ <target port='0'/>
+ </serial>
+ <console type='pty'>
+ <target type='serial' port='0'/>
+ </console>
+ <channel type='spicevmc'>
+ <target type='virtio' name='com.redhat.spice.0'/>
+ <address type='virtio-serial' controller='0' bus='0' port='1'/>
+ </channel>
+ <input type='tablet' bus='usb'/>
+ <input type='mouse' bus='ps2'/>
+ <graphics type='spice' autoport='yes' listen='0.0.0.0'>
+ <listen type='address' address='0.0.0.0'/>
+ </graphics>
+ <sound model='ich6'>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x04' function='0x0'/>
+ </sound>
+ <video>
+ <model type='cirrus' vram='9216' heads='1'/>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x02' function='0x0'/>
+ </video>
+ <memballoon model='virtio'>
+ <address type='pci' domain='0x0000'
+ bus='0x00' slot='0x0a' function='0x0'/>
+ </memballoon>
+ </devices>
+</domain>
+EOF;
+
+ $resource->logEvent(
+ DrydockKVMCreatingVMLogType::LOGCONST,
+ array(
+ 'vm-name' => $vm_name,
+ ));
+
+ $future = $this->getSSHFuture(
+ $blueprint,
+ $loaded_credential,
+ '/usr/bin/virsh create /dev/stdin');
+ $future->write($xml);
+ $future->resolvex();
+
+ $resource->logEvent(
+ DrydockKVMCreatedVMLogType::LOGCONST,
+ array(
+ 'vm-name' => $vm_name,
+ ));
+
+ $resource
+ ->setStatus(DrydockResourceStatus::STATUS_PENDING)
+ ->setAttributes(array(
+ 'platform' => $blueprint->getFieldValue('platform'),
+ 'protocol' => 'ssh',
+ 'path' => $blueprint->getFieldValue('storage-path'),
+ 'keypair' => $blueprint->getFieldValue('keypair'),
+ 'winrm-auth' => $winrm_auth_id,
+ 'vm-name' => $vm_name,
+ 'image-name' => $image_name,
+ 'image-path' => $image_path,
+ 'storage-pool' => $blueprint->getFieldValue('storage-pool'),
+ ))
+ ->save();
+
+ $resource->logEvent(
+ DrydockKVMRetrievingMACAddressLogType::LOGCONST,
+ array(
+ 'vm-name' => $vm_name,
+ ));
+
+ $future = $this->getSSHFuture(
+ $blueprint,
+ $loaded_credential,
+ '/usr/bin/virsh dumpxml %s',
+ $vm_name);
+ list($stdout, $stderr) = $future->resolvex();
+ $status_xml = simplexml_load_string(trim($stdout));
+ if ($status_xml === false) {
+ throw new Exception('Unable to read VM XML!');
+ }
+
+ $mac_address = $status_xml->devices->interface->mac->attributes()->address;
+
+ $resource->logEvent(
+ DrydockKVMRetrievedMACAddressLogType::LOGCONST,
+ array(
+ 'vm-name' => $vm_name,
+ 'mac-address' => $mac_address,
+ ));
+
+ $resource->setAttribute('mac-address', $mac_address);
+
+ if ($resource->getAttribute('platform') === 'windows') {
+ $resource->setAttribute('port', 5985);
+ } else {
+ $resource->setAttribute('port', 22);
+ }
+
+ $resource->setAttribute('state', self::RESOURCE_STATE_WAIT_FOR_IP_ADDRESS);
+ $resource->save();
+
+ $resource->logEvent(
+ DrydockKVMWaitingForIPAddressLogType::LOGCONST,
+ array());
+
+ throw new PhabricatorWorkerYieldException(0);
+ }
+
+ private function checkIfIPAddressIsAssigned(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource = null,
+ DrydockLease $lease = null) {
+
+ $loaded_credential = PassphraseSSHKey::loadFromPHID(
+ $blueprint->getFieldValue('keypair'),
+ PhabricatorUser::getOmnipotentUser());
+
+ $mac_address = $resource->getAttribute('mac-address');
+ if (is_array($mac_address)) {
+ $mac_address = head($mac_address);
+ }
+
+ $future = $this->getSSHFuture(
+ $blueprint,
+ $loaded_credential,
+ 'cat %s',
+ $blueprint->getFieldValue('dnsmasq-path'));
+ list($stdout, $stderr) = $future->resolvex();
+
+ $ip_address = null;
+ if ($blueprint->getFieldValue('dnsmasq-is-json')) {
+ $json = phutil_json_decode($stdout);
+ foreach ($json as $entry) {
+ if (idx($entry, 'mac-address') === trim($mac_address)) {
+ $ip_address = idx($entry, 'ip-address');
+ break;
+ }
+ }
+ } else {
+ foreach (phutil_split_lines($stdout) as $line) {
+ $components = explode(' ', $line);
+ if (trim($components[1]) === trim($mac_address)) {
+ $ip_address = $components[2];
+ break;
+ }
+ }
+ }
+
+ if ($ip_address != null) {
+ // Now we save the IP address depending on why we need
+ // the IP address. If we have a resource, this IP address
+ // will be used to establish initial connectivity over
+ // WinRM. If we have a lease, this IP address will be used
+ // to execute commands on the lease.
+ if ($lease !== null) {
+ $lease->setAttribute(
+ 'ip-address',
+ $ip_address);
+ $lease->setAttribute(
+ 'state',
+ self::LEASE_STATE_READY);
+ $lease->save();
+ } else if ($resource !== null) {
+ $resource->logEvent(
+ DrydockKVMAssignedIPAddressLogType::LOGCONST,
+ array(
+ 'vm-name' => $resource->getAttribute('vm-name'),
+ 'ip-address' => $ip_address,
+ ));
+ $resource->setAttribute(
+ 'ip-address-for-connectivity',
+ $ip_address);
+ $resource->setAttribute(
+ 'state',
+ self::RESOURCE_STATE_TEST_CONNECTIVITY);
+ $resource->logEvent(
+ DrydockKVMWaitingForConnectivityLogType::LOGCONST,
+ array(
+ 'protocol' => $resource->getAttribute('protocol'),
+ ));
+ $resource->save();
+ }
+
+ // We have moved to the next stage.
+ throw new PhabricatorWorkerYieldException(0);
+ }
+
+ $future = $this->getSSHFuture(
+ $blueprint,
+ $loaded_credential,
+ '/usr/bin/virsh domstate %s',
+ $resource->getAttribute('vm-name'));
+ list($stdout, $stderr) = $future->resolvex();
+ $status = trim($stdout).trim($stderr);
+ if (substr_count($status, 'shut off') > 0 ||
+ substr_count($status, 'Domain not found') > 0) {
+ throw new DrydockKVMVirtualMachineShutdownException();
+ }
+
+ // The machine isn't yet assigned an IP address, so yield for
+ // 10 seconds to let the VM spin up.
+ throw new PhabricatorWorkerYieldException(10);
+ }
+
+ private function testConnectivity(
+ DrydockBlueprint $blueprint,
+ DrydockResource $resource) {
+
+ $loaded_credential = PassphraseSSHKey::loadFromPHID(
+ $blueprint->getFieldValue('keypair'),
+ PhabricatorUser::getOmnipotentUser());
+
+ $ip_address = $resource->getAttribute('ip-address-for-connectivity');
+
+ $resource->logEvent(
+ DrydockKVMAttemptConnectionLogType::LOGCONST,
+ array(
+ 'protocol' => $resource->getAttribute('protocol'),
+ 'vm-name' => $resource->getAttribute('vm-name'),
+ 'ip-address' => $ip_address,
+ ));
+
+ $interface = $this->getCommandInterfaceWithExplicitHost(
+ $blueprint,
+ $resource,
+ $resource->getAttribute('ip-address-for-connectivity'));
+ $interface->enableConnectionDebugging();
+ $interface->setExecTimeout(60);
+ if ($resource->getAttribute('protocol') !== 'winrm') {
+ $interface->setConnectTimeout(60);
+ }
+
+ try {
+ $future = $interface->getExecFuture('echo "test"');
+ $future->resolvex();
+ if ($future->getWasKilledByTimeout()) {
+ throw new CommandException(
+ 'Command timed out.',
+ 'echo "test"',
+ -255,
+ 'timeout',
+ '');
+ }
+
+ $resource->unsetAttribute('ip-address-for-connectivity');
+ $resource->setAttribute(
+ 'state',
+ self::RESOURCE_STATE_READY);
+ $resource->logEvent(
+ DrydockKVMSuccessfulConnectionLogType::LOGCONST,
+ array(
+ 'protocol' => $resource->getAttribute('protocol'),
+ 'vm-name' => $resource->getAttribute('vm-name'),
+ 'ip-address' => $ip_address,
+ ));
+ $resource->save();
+
+ // We have moved to the next stage.
+ throw new PhabricatorWorkerYieldException(0);
+ } catch (CommandException $ex) {
+ // Fallthrough to perform the status check.
+ $resource->logEvent(
+ DrydockKVMFailedConnectionLogType::LOGCONST,
+ array(
+ 'protocol' => $resource->getAttribute('protocol'),
+ 'vm-name' => $resource->getAttribute('vm-name'),
+ 'message' => $ex->getMessage(),
+ 'stacktrace' => $ex->getTraceAsString(),
+ 'stdout' => $ex->getStdout(),
+ 'stderr' => $ex->getStderr(),
+ ));
+ }
+
+ $future = $this->getSSHFuture(
+ $blueprint,
+ $loaded_credential,
+ '/usr/bin/virsh domstate %s',
+ $resource->getAttribute('vm-name'));
+ list($stdout, $stderr) = $future->resolvex();
+ $status = trim($stdout).trim($stderr);
+ if (substr_count($status, 'shut off') > 0 ||
+ substr_count($status, 'Domain not found') > 0) {
+ throw new Exception('Virtual machine no longer exists or is shut down');
+ }
+
+ // The machine isn't yet serving remote connections, so yield for
+ // 10 seconds to let the VM start services.
+ throw new PhabricatorWorkerYieldException(10);
+ }
+
+ private function getSSHFuture(
+ DrydockBlueprint $blueprint,
+ $credential,
+ $command) {
+
+ $argv = func_get_args();
+ array_shift($argv);
+ array_shift($argv);
+ $future = new ExecFuture(
+ 'ssh '.
+ '-o LogLevel=quiet '.
+ '-o StrictHostKeyChecking=no '.
+ '-o UserKnownHostsFile=/dev/null '.
+ '-o BatchMode=yes '.
+ '-p %s -i %P %P@%s -- %s',
+ $blueprint->getFieldValue('port'),
+ $credential->getKeyfileEnvelope(),
+ $credential->getUsernameEnvelope(),
+ $blueprint->getFieldValue('host'),
+ call_user_func_array('csprintf', $argv));
+ return $future;
+ }
+}
diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
--- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
@@ -102,6 +102,18 @@
DrydockResource $resource,
DrydockLease $lease) {
+ $host_lease = id(new DrydockLeaseQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs(array($resource->getAttribute('host.leasePHID')))
+ ->executeOne();
+ if ($host_lease === null || !$host_lease->isActive()) {
+ $resource->setStatus(DrydockResourceStatus::STATUS_BROKEN);
+ $resource->save();
+ $resource->scheduleUpdate();
+
+ throw new Exception('Host lease is no longer active');
+ }
+
$lease
->needSlotLock($this->getLeaseSlotLock($resource))
->acquireOnResource($resource);
@@ -176,6 +188,7 @@
$path = "{$root}/repo/{$directory}/";
// TODO: Run these in parallel?
+ $interface->enableConnectionDebugging();
$interface->execx(
'git clone -- %s %s',
(string)$repository->getCloneURIObject(),
diff --git a/src/applications/drydock/exception/DrydockKVMVirtualMachineShutdownException.php b/src/applications/drydock/exception/DrydockKVMVirtualMachineShutdownException.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/exception/DrydockKVMVirtualMachineShutdownException.php
@@ -0,0 +1,10 @@
+<?php
+
+final class DrydockKVMVirtualMachineShutdownException
+ extends PhabricatorWorkerYieldException {
+
+ public function __construct() {
+ parent::__construct(15);
+ }
+
+}
diff --git a/src/applications/drydock/interface/command/DrydockCommandInterface.php b/src/applications/drydock/interface/command/DrydockCommandInterface.php
--- a/src/applications/drydock/interface/command/DrydockCommandInterface.php
+++ b/src/applications/drydock/interface/command/DrydockCommandInterface.php
@@ -5,6 +5,10 @@
const INTERFACE_TYPE = 'command';
private $workingDirectoryStack = array();
+ private $sshProxyHost;
+ private $sshProxyPort;
+ private $sshProxyCredential;
+ private $debugConnection;
public function pushWorkingDirectory($working_directory) {
$this->workingDirectoryStack[] = $working_directory;
@@ -27,10 +31,49 @@
return null;
}
+ public function enableConnectionDebugging() {
+ $this->debugConnection = true;
+ return $this;
+ }
+
+ protected function getConnectionDebugging() {
+ return $this->debugConnection;
+ }
+
final public function getInterfaceType() {
return self::INTERFACE_TYPE;
}
+ public function setSSHProxy($host, $port, $credential) {
+ $this->sshProxyHost = $host;
+ $this->sshProxyPort = $port;
+ $this->sshProxyCredential = $credential;
+ return $this;
+ }
+
+ public function getSSHProxyCommand() {
+ if ($this->sshProxyHost === null) {
+ return '';
+ }
+
+ return csprintf(
+ 'ssh '.
+ '-o LogLevel=%s '.
+ '-o StrictHostKeyChecking=no '.
+ '-o UserKnownHostsFile=/dev/null '.
+ '-o BatchMode=yes '.
+ '-p %s -i %P %P@%s --',
+ $this->getConnectionDebugging() ? 'debug' : 'quiet',
+ $this->sshProxyPort,
+ $this->sshProxyCredential->getKeyfileEnvelope(),
+ $this->sshProxyCredential->getUsernameEnvelope(),
+ $this->sshProxyHost);
+ }
+
+ public function isSSHProxied() {
+ return $this->sshProxyHost !== null;
+ }
+
final public function exec($command) {
$argv = func_get_args();
$exec = call_user_func_array(
diff --git a/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php b/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php
--- a/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php
+++ b/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php
@@ -4,6 +4,8 @@
private $credential;
private $connectTimeout;
+ private $execTimeout;
+ private $remoteKeyFile;
private function loadCredential() {
if ($this->credential === null) {
@@ -22,6 +24,11 @@
return $this;
}
+ public function setExecTimeout($timeout) {
+ $this->execTimeout = $timeout;
+ return $this;
+ }
+
public function getExecFuture($command) {
$credential = $this->loadCredential();
@@ -31,7 +38,8 @@
$flags = array();
$flags[] = '-o';
- $flags[] = 'LogLevel=quiet';
+ $flags[] = 'LogLevel='.(
+ $this->getConnectionDebugging() ? 'debug' : 'quiet');
$flags[] = '-o';
$flags[] = 'StrictHostKeyChecking=no';
@@ -47,13 +55,52 @@
$flags[] = 'ConnectTimeout='.$this->connectTimeout;
}
- return new ExecFuture(
+ $key = $credential->getKeyfileEnvelope();
+ if ($this->isSSHProxied()) {
+ if ($this->remoteKeyFile === null) {
+ $temp_name = '/tmp/'.Filesystem::readRandomCharacters(20).'.proxy';
+ $key_future = new ExecFuture(
+ 'cat %P | %C %s',
+ $key,
+ $this->getSSHProxyCommand(),
+ csprintf(
+ 'touch %s && chmod 0600 %s && cat - >%s',
+ $temp_name,
+ $temp_name,
+ $temp_name));
+ $key_future->resolvex();
+ $this->remoteKeyFile = new RemoteTempFile(
+ $temp_name,
+ new ExecFuture(
+ '%C %s',
+ $this->getSSHProxyCommand(),
+ csprintf('rm %s', $temp_name)));
+ }
+ $key = new PhutilOpaqueEnvelope((string)$this->remoteKeyFile);
+ }
+
+ $escaped_command = csprintf(
'ssh %Ls -l %P -p %s -i %P %s -- %s',
$flags,
$credential->getUsernameEnvelope(),
$this->getConfig('port'),
- $credential->getKeyfileEnvelope(),
+ $key,
$this->getConfig('host'),
$full_command);
+
+ $proxy_cmd = $this->getSSHProxyCommand();
+ if ($proxy_cmd !== '') {
+ $future = new ExecFuture(
+ '%C %s',
+ $proxy_cmd,
+ $escaped_command);
+ } else {
+ $future = new ExecFuture(
+ '%C',
+ $escaped_command);
+ }
+
+ $future->setTimeout($this->execTimeout);
+ return $future;
}
}
diff --git a/src/applications/drydock/interface/command/RemoteTempFile.php b/src/applications/drydock/interface/command/RemoteTempFile.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/interface/command/RemoteTempFile.php
@@ -0,0 +1,31 @@
+<?php
+
+final class RemoteTempFile extends Phobject {
+
+ private $file;
+ private $destroyed = false;
+ private $destructionFuture;
+
+ public function __construct($filename, $destruction_future) {
+ $this->file = $filename;
+ $this->destructionFuture = $destruction_future;
+
+ register_shutdown_function(array($this, '__destruct'));
+ }
+
+ public function __toString() {
+ return $this->file;
+ }
+
+ public function __destruct() {
+ if ($this->destroyed) {
+ return;
+ }
+
+ $this->destructionFuture->resolve();
+
+ $this->file = null;
+ $this->destroyed = true;
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMAllocatedImageLogType.php b/src/applications/drydock/logtype/DrydockKVMAllocatedImageLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMAllocatedImageLogType.php
@@ -0,0 +1,24 @@
+<?php
+
+final class DrydockKVMAllocatedImageLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.image.allocated';
+
+ public function getLogTypeName() {
+ return pht('Allocated Image');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $base_image = idx($data, 'base-image', null);
+ $new_image = idx($data, 'new-image', null);
+ return pht(
+ 'Allocated new image from "%s" as "%s".',
+ $base_image,
+ $new_image);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMAllocatingImageLogType.php b/src/applications/drydock/logtype/DrydockKVMAllocatingImageLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMAllocatingImageLogType.php
@@ -0,0 +1,24 @@
+<?php
+
+final class DrydockKVMAllocatingImageLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.image.allocating';
+
+ public function getLogTypeName() {
+ return pht('Allocating Image');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $base_image = idx($data, 'base-image', null);
+ $new_image = idx($data, 'new-image', null);
+ return pht(
+ 'Allocating new image from "%s" as "%s".',
+ $base_image,
+ $new_image);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMAssignedIPAddressLogType.php b/src/applications/drydock/logtype/DrydockKVMAssignedIPAddressLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMAssignedIPAddressLogType.php
@@ -0,0 +1,24 @@
+<?php
+
+final class DrydockKVMAssignedIPAddressLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.network.assigned-ip-address';
+
+ public function getLogTypeName() {
+ return pht('Assigned IP Address');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+ $ip_address = idx($data, 'ip-address', null);
+ return pht(
+ 'Virtual machine "%s" currently has an IP address of "%s".',
+ $vm_name,
+ $ip_address);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMAttemptConnectionLogType.php b/src/applications/drydock/logtype/DrydockKVMAttemptConnectionLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMAttemptConnectionLogType.php
@@ -0,0 +1,29 @@
+<?php
+
+final class DrydockKVMAttemptConnectionLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.network.attempt-connection';
+
+ public function getLogTypeName() {
+ return pht('Attempting Connection');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+ if (idx($data, 'protocol', null) === 'winrm') {
+ $protocol_name = 'WinRM';
+ } else {
+ $protocol_name = 'SSH';
+ }
+
+ return pht(
+ 'Attempting to connect to "%s" via %s',
+ $vm_name,
+ $protocol_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMCreatedVMLogType.php b/src/applications/drydock/logtype/DrydockKVMCreatedVMLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMCreatedVMLogType.php
@@ -0,0 +1,22 @@
+<?php
+
+final class DrydockKVMCreatedVMLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.vm.created';
+
+ public function getLogTypeName() {
+ return pht('Created VM');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+ return pht(
+ 'Created new virtual machine "%s".',
+ $vm_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMCreatingVMLogType.php b/src/applications/drydock/logtype/DrydockKVMCreatingVMLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMCreatingVMLogType.php
@@ -0,0 +1,22 @@
+<?php
+
+final class DrydockKVMCreatingVMLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.vm.creating';
+
+ public function getLogTypeName() {
+ return pht('Creating VM');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+ return pht(
+ 'Creating new virtual machine "%s".',
+ $vm_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMCredentialUsageLogType.php b/src/applications/drydock/logtype/DrydockKVMCredentialUsageLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMCredentialUsageLogType.php
@@ -0,0 +1,26 @@
+<?php
+
+final class DrydockKVMCredentialUsageLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.credential.usage';
+
+ public function getLogTypeName() {
+ return pht('Credential Usage');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $phid = idx($data, 'phid', null);
+ if ($phid) {
+ return pht(
+ 'Using credential %s to allocate.',
+ $phid);
+ } else {
+ return pht('No associated credential data.');
+ }
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMFailedConnectionLogType.php b/src/applications/drydock/logtype/DrydockKVMFailedConnectionLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMFailedConnectionLogType.php
@@ -0,0 +1,48 @@
+<?php
+
+final class DrydockKVMFailedConnectionLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.network.failed-connection';
+
+ public function getLogTypeName() {
+ return pht('Failed Connection');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+ $stdout = idx($data, 'stdout', null);
+ $stderr = idx($data, 'stderr', null);
+
+ $stdout = phutil_split_lines($stdout);
+ $stderr = phutil_split_lines($stderr);
+
+ $formatted_stdout = array();
+ $formatted_stderr = array();
+ foreach ($stdout as $line) {
+ $formatted_stdout[] = $line;
+ $formatted_stdout[] = phutil_tag('br', array(), null);
+ }
+ foreach ($stderr as $line) {
+ $formatted_stderr[] = $line;
+ $formatted_stderr[] = phutil_tag('br', array(), null);
+ }
+
+ array_pop($formatted_stderr);
+
+ return array(
+ pht('Unable to connect to "%s":', $vm_name),
+ phutil_tag('br', array(), null),
+ pht('STDOUT'),
+ phutil_tag('br', array(), null),
+ $formatted_stdout,
+ pht('STDERR'),
+ phutil_tag('br', array(), null),
+ $formatted_stderr,
+ );
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMRemovedImageLogType.php b/src/applications/drydock/logtype/DrydockKVMRemovedImageLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMRemovedImageLogType.php
@@ -0,0 +1,22 @@
+<?php
+
+final class DrydockKVMRemovedImageLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.image.removed';
+
+ public function getLogTypeName() {
+ return pht('Removed Image');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $image_name = idx($data, 'image-name', null);
+ return pht(
+ 'Removed image "%s".',
+ $image_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMRemovingImageLogType.php b/src/applications/drydock/logtype/DrydockKVMRemovingImageLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMRemovingImageLogType.php
@@ -0,0 +1,22 @@
+<?php
+
+final class DrydockKVMRemovingImageLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.image.removing';
+
+ public function getLogTypeName() {
+ return pht('Removing Image');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $image_name = idx($data, 'image-name', null);
+ return pht(
+ 'Removing image "%s"...',
+ $image_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMRetrievedImagePathLogType.php b/src/applications/drydock/logtype/DrydockKVMRetrievedImagePathLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMRetrievedImagePathLogType.php
@@ -0,0 +1,24 @@
+<?php
+
+final class DrydockKVMRetrievedImagePathLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.image.retrieved-path';
+
+ public function getLogTypeName() {
+ return pht('Retrieved Image Path');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $new_image = idx($data, 'new-image', null);
+ $path = idx($data, 'path', null);
+ return pht(
+ 'Retrieved image path of "%s" as "%s".',
+ $new_image,
+ $path);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMRetrievedMACAddressLogType.php b/src/applications/drydock/logtype/DrydockKVMRetrievedMACAddressLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMRetrievedMACAddressLogType.php
@@ -0,0 +1,24 @@
+<?php
+
+final class DrydockKVMRetrievedMACAddressLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.network.retrieved-mac-address';
+
+ public function getLogTypeName() {
+ return pht('Retrieved MAC Address');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+ $mac_address = idx($data, 'mac-address', null);
+ return pht(
+ 'MAC address of virtual machine "%s" is "%s".',
+ $vm_name,
+ $mac_address);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMRetrievingImagePathLogType.php b/src/applications/drydock/logtype/DrydockKVMRetrievingImagePathLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMRetrievingImagePathLogType.php
@@ -0,0 +1,22 @@
+<?php
+
+final class DrydockKVMRetrievingImagePathLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.image.retrieving-path';
+
+ public function getLogTypeName() {
+ return pht('Retrieving Image Path');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $new_image = idx($data, 'new-image', null);
+ return pht(
+ 'Retrieving path to new image "%s".',
+ $new_image);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMRetrievingMACAddressLogType.php b/src/applications/drydock/logtype/DrydockKVMRetrievingMACAddressLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMRetrievingMACAddressLogType.php
@@ -0,0 +1,22 @@
+<?php
+
+final class DrydockKVMRetrievingMACAddressLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.network.retrieving-mac-address';
+
+ public function getLogTypeName() {
+ return pht('Retrieving MAC Address');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+ return pht(
+ 'Retrieving MAC address of virtual machine "%s".',
+ $vm_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMShutdownVMLogType.php b/src/applications/drydock/logtype/DrydockKVMShutdownVMLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMShutdownVMLogType.php
@@ -0,0 +1,22 @@
+<?php
+
+final class DrydockKVMShutdownVMLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.vm.shutdown';
+
+ public function getLogTypeName() {
+ return pht('Shutdown VM');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+ return pht(
+ 'Shut down of virtual machine "%s" complete.',
+ $vm_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMShuttingDownVMLogType.php b/src/applications/drydock/logtype/DrydockKVMShuttingDownVMLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMShuttingDownVMLogType.php
@@ -0,0 +1,22 @@
+<?php
+
+final class DrydockKVMShuttingDownVMLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.vm.shutting-down';
+
+ public function getLogTypeName() {
+ return pht('Shutting Down VM');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+ return pht(
+ 'Shutting down virtual machine "%s"...',
+ $vm_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMSuccessfulConnectionLogType.php b/src/applications/drydock/logtype/DrydockKVMSuccessfulConnectionLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMSuccessfulConnectionLogType.php
@@ -0,0 +1,23 @@
+<?php
+
+final class DrydockKVMSuccessfulConnectionLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.network.successful-connection';
+
+ public function getLogTypeName() {
+ return pht('Successful Connection');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+
+ return pht(
+ 'Connected successfully to "%s"',
+ $vm_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMUncleanShutdownLogType.php b/src/applications/drydock/logtype/DrydockKVMUncleanShutdownLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMUncleanShutdownLogType.php
@@ -0,0 +1,51 @@
+<?php
+
+final class DrydockKVMUncleanShutdownLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.vm.unclean-shutdown';
+
+ public function getLogTypeName() {
+ return pht('Unclean Shutdown');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+ $image_name = idx($data, 'image-name', null);
+ $stdout = idx($data, 'stdout', null);
+ $stderr = idx($data, 'stderr', null);
+
+ $stdout = phutil_split_lines($stdout);
+ $stderr = phutil_split_lines($stderr);
+
+ $formatted_stdout = array();
+ $formatted_stderr = array();
+ foreach ($stdout as $line) {
+ $formatted_stdout[] = $line;
+ $formatted_stdout[] = phutil_tag('br', array(), null);
+ }
+ foreach ($stderr as $line) {
+ $formatted_stderr[] = $line;
+ $formatted_stderr[] = phutil_tag('br', array(), null);
+ }
+
+ array_pop($formatted_stderr);
+
+ return array(
+ pht(
+ 'Unable to cleanly shutdown VM "%s" (using image "%s"). Was the VM '.
+ 'already shut down?', $vm_name, $image_name),
+ phutil_tag('br', array(), null),
+ pht('STDOUT'),
+ phutil_tag('br', array(), null),
+ $formatted_stdout,
+ pht('STDERR'),
+ phutil_tag('br', array(), null),
+ $formatted_stderr,
+ );
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMUnexpectedVMShutdownLogType.php b/src/applications/drydock/logtype/DrydockKVMUnexpectedVMShutdownLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMUnexpectedVMShutdownLogType.php
@@ -0,0 +1,23 @@
+<?php
+
+final class DrydockKVMUnexpectedVMShutdownLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.network.vm-shutdown';
+
+ public function getLogTypeName() {
+ return pht('Unexpected VM Shutdown');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return 'fa-times red';
+ }
+
+ public function renderLog(array $data) {
+ $vm_name = idx($data, 'vm-name', null);
+
+ return pht(
+ 'Virtual machine "%s" no longer exists or is shut down',
+ $vm_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMWaitingForConnectivityLogType.php b/src/applications/drydock/logtype/DrydockKVMWaitingForConnectivityLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMWaitingForConnectivityLogType.php
@@ -0,0 +1,28 @@
+<?php
+
+final class DrydockKVMWaitingForConnectivityLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.network.waiting-for-connectivity';
+
+ public function getLogTypeName() {
+ return pht('Waiting for Connectivity');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $protocol_name = '';
+ if (idx($data, 'protocol', null) === 'winrm') {
+ $protocol_name = 'WinRM';
+ } else {
+ $protocol_name = 'SSH';
+ }
+
+ return pht(
+ 'Waiting for a successful %s connection',
+ $protocol_name);
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMWaitingForIPAddressLogType.php b/src/applications/drydock/logtype/DrydockKVMWaitingForIPAddressLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMWaitingForIPAddressLogType.php
@@ -0,0 +1,20 @@
+<?php
+
+final class DrydockKVMWaitingForIPAddressLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.network.waiting-for-ip-address';
+
+ public function getLogTypeName() {
+ return pht('Waiting for IP Address');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ return pht(
+ 'Waiting until the virtual machine is allocated an IP address...');
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockKVMWinRMCredentialUsageLogType.php b/src/applications/drydock/logtype/DrydockKVMWinRMCredentialUsageLogType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/logtype/DrydockKVMWinRMCredentialUsageLogType.php
@@ -0,0 +1,26 @@
+<?php
+
+final class DrydockKVMWinRMCredentialUsageLogType extends DrydockLogType {
+
+ const LOGCONST = 'kvm.credential.winrm.usage';
+
+ public function getLogTypeName() {
+ return pht('WinRM Credential Usage');
+ }
+
+ public function getLogTypeIcon(array $data) {
+ return '';
+ }
+
+ public function renderLog(array $data) {
+ $phid = idx($data, 'phid', null);
+ if ($phid) {
+ return pht(
+ 'Using credential %s to authenticate over WinRM.',
+ $phid);
+ } else {
+ return pht('No associated credential data.');
+ }
+ }
+
+}
diff --git a/src/applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php b/src/applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php
--- a/src/applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php
+++ b/src/applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php
@@ -15,8 +15,40 @@
public function renderLog(array $data) {
$class = idx($data, 'class');
$message = idx($data, 'message');
+ $stdout = idx($data, 'stdout', null);
+ $stderr = idx($data, 'stderr', null);
- return pht('Lease activation failed: [%s] %s', $class, $message);
+ $primary = pht('Lease activation failed: [%s] %s', $class, $message);
+ if ($stdout !== null || $stderr !== null) {
+ $stdout = phutil_split_lines($stdout);
+ $stderr = phutil_split_lines($stderr);
+
+ $formatted_stdout = array();
+ $formatted_stderr = array();
+ foreach ($stdout as $line) {
+ $formatted_stdout[] = $line;
+ $formatted_stdout[] = phutil_tag('br', array(), null);
+ }
+ foreach ($stderr as $line) {
+ $formatted_stderr[] = $line;
+ $formatted_stderr[] = phutil_tag('br', array(), null);
+ }
+
+ array_pop($formatted_stderr);
+
+ $primary = array(
+ $primary,
+ phutil_tag('br', array(), null),
+ pht('STDOUT'),
+ phutil_tag('br', array(), null),
+ $formatted_stdout,
+ pht('STDERR'),
+ phutil_tag('br', array(), null),
+ $formatted_stderr,
+ );
+ }
+
+ return $primary;
}
}
diff --git a/src/applications/drydock/logtype/DrydockResourceActivationFailureLogType.php b/src/applications/drydock/logtype/DrydockResourceActivationFailureLogType.php
--- a/src/applications/drydock/logtype/DrydockResourceActivationFailureLogType.php
+++ b/src/applications/drydock/logtype/DrydockResourceActivationFailureLogType.php
@@ -15,8 +15,40 @@
public function renderLog(array $data) {
$class = idx($data, 'class');
$message = idx($data, 'message');
+ $stdout = idx($data, 'stdout', null);
+ $stderr = idx($data, 'stderr', null);
- return pht('Resource activation failed: [%s] %s', $class, $message);
+ $primary = pht('Resource activation failed: [%s] %s', $class, $message);
+ if ($stdout !== null || $stderr !== null) {
+ $stdout = phutil_split_lines($stdout);
+ $stderr = phutil_split_lines($stderr);
+
+ $formatted_stdout = array();
+ $formatted_stderr = array();
+ foreach ($stdout as $line) {
+ $formatted_stdout[] = $line;
+ $formatted_stdout[] = phutil_tag('br', array(), null);
+ }
+ foreach ($stderr as $line) {
+ $formatted_stderr[] = $line;
+ $formatted_stderr[] = phutil_tag('br', array(), null);
+ }
+
+ array_pop($formatted_stderr);
+
+ $primary = array(
+ $primary,
+ phutil_tag('br', array(), null),
+ pht('STDOUT'),
+ phutil_tag('br', array(), null),
+ $formatted_stdout,
+ pht('STDERR'),
+ phutil_tag('br', array(), null),
+ $formatted_stderr,
+ );
+ }
+
+ return $primary;
}
}
diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php
--- a/src/applications/drydock/storage/DrydockResource.php
+++ b/src/applications/drydock/storage/DrydockResource.php
@@ -66,6 +66,11 @@
return $this;
}
+ public function unsetAttribute($key) {
+ unset($this->attributes[$key]);
+ return $this;
+ }
+
public function getCapability($key, $default = null) {
return idx($this->capbilities, $key, $default);
}
diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
--- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
+++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php
@@ -757,6 +757,8 @@
array(
'class' => get_class($ex),
'message' => $ex->getMessage(),
+ 'stdout' => ($ex instanceof CommandException) ? $ex->getStdout() : null,
+ 'stderr' => ($ex instanceof CommandException) ? $ex->getStderr() : null,
));
$lease->awakenTasks();
diff --git a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php
--- a/src/applications/drydock/worker/DrydockResourceUpdateWorker.php
+++ b/src/applications/drydock/worker/DrydockResourceUpdateWorker.php
@@ -251,7 +251,9 @@
DrydockResourceActivationFailureLogType::LOGCONST,
array(
'class' => get_class($ex),
- 'message' => $ex->getMessage(),
+ 'message' => $ex->getMessage().$ex->getTraceAsString(),
+ 'stdout' => ($ex instanceof CommandException) ? $ex->getStdout() : null,
+ 'stderr' => ($ex instanceof CommandException) ? $ex->getStderr() : null,
));
throw new PhabricatorWorkerPermanentFailureException(
diff --git a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php
--- a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php
+++ b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php
@@ -12,38 +12,84 @@
}
public function getBuildStepGroupKey() {
- return HarbormasterPrototypeBuildStepGroup::GROUPKEY;
+ return HarbormasterDrydockBuildStepGroup::GROUPKEY;
}
public function execute(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target) {
+ $viewer = PhabricatorUser::getOmnipotentUser();
$settings = $this->getSettings();
- // Create the lease.
- $lease = id(new DrydockLease())
- ->setResourceType('host')
- ->setOwnerPHID($build_target->getPHID())
- ->setAttributes(
+ // TODO: We should probably have a separate temporary storage area for
+ // execution stuff that doesn't step on configuration state?
+ $lease_phid = $build_target->getDetail('exec.leasePHID');
+
+ if ($lease_phid) {
+ $lease = id(new DrydockLeaseQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($lease_phid))
+ ->executeOne();
+ if (!$lease) {
+ throw new PhabricatorWorkerPermanentFailureException(
+ pht(
+ 'Lease "%s" could not be loaded.',
+ $lease_phid));
+ }
+ } else {
+ $working_copy_type =
+ id(new DrydockAlmanacServiceHostBlueprintImplementation())
+ ->getType();
+
+ $allowed_phids = $build_target->getFieldValue('blueprintPHIDs');
+ if (!is_array($allowed_phids)) {
+ $allowed_phids = array();
+ }
+ $authorizing_phid = $build_target->getBuildStep()->getPHID();
+
+ $lease = DrydockLease::initializeNewLease()
+ ->setResourceType($working_copy_type)
+ ->setOwnerPHID($build_target->getPHID())
+ ->setAuthorizingPHID($authorizing_phid)
+ ->setAllowedBlueprintPHIDs($allowed_phids);
+
+ $task_id = $this->getCurrentWorkerTaskID();
+ if ($task_id) {
+ $lease->setAwakenTaskIDs(array($task_id));
+ }
+
+ // TODO: Maybe add a method to mark artifacts like this as pending?
+
+ // Create the artifact now so that the lease is always disposed of, even
+ // if this target is aborted.
+ $build_target->createArtifact(
+ $viewer,
+ $settings['name'],
+ HarbormasterWorkingCopyArtifact::ARTIFACTCONST,
array(
- 'platform' => $settings['platform'],
- ))
- ->queueForActivation();
-
- // Wait until the lease is fulfilled.
- // TODO: This will throw an exception if the lease can't be fulfilled;
- // we should treat that as build failure not build error.
- $lease->waitUntilActive();
-
- // Create the associated artifact.
- $artifact = $build_target->createArtifact(
- PhabricatorUser::getOmnipotentUser(),
- $settings['name'],
- HarbormasterHostArtifact::ARTIFACTCONST,
- array(
- 'drydockLeasePHID' => $lease->getPHID(),
- ));
+ 'drydockLeasePHID' => $lease->getPHID(),
+ ));
+
+ $lease->queueForActivation();
+
+ $build_target
+ ->setDetail('exec.leasePHID', $lease->getPHID())
+ ->save();
+ }
+
+ if ($lease->isActivating()) {
+ // TODO: Smart backoff?
+ throw new PhabricatorWorkerYieldException(15);
+ }
+
+ if (!$lease->isActive()) {
+ // TODO: We could just forget about this lease and retry?
+ throw new PhabricatorWorkerPermanentFailureException(
+ pht(
+ 'Lease "%s" never activated.',
+ $lease->getPHID()));
+ }
}
public function getArtifactOutputs() {
@@ -63,9 +109,9 @@
'type' => 'text',
'required' => true,
),
- 'platform' => array(
- 'name' => pht('Platform'),
- 'type' => 'text',
+ 'blueprintPHIDs' => array(
+ 'name' => pht('Use Blueprints'),
+ 'type' => 'blueprints',
'required' => true,
),
);
diff --git a/src/infrastructure/daemon/workers/exception/PhabricatorWorkerYieldException.php b/src/infrastructure/daemon/workers/exception/PhabricatorWorkerYieldException.php
--- a/src/infrastructure/daemon/workers/exception/PhabricatorWorkerYieldException.php
+++ b/src/infrastructure/daemon/workers/exception/PhabricatorWorkerYieldException.php
@@ -6,7 +6,7 @@
* If a worker throws this exception while processing a task, the task will be
* pushed toward the back of the queue and tried again later.
*/
-final class PhabricatorWorkerYieldException extends Exception {
+class PhabricatorWorkerYieldException extends Exception {
private $duration;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Dec 13, 3:43 PM (49 m, 25 s)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6876598
Default Alt Text
D14583.diff (70 KB)
Attached To
Mode
D14583: [drydock/kvm] Implement a KVM virtual machine allocator for Drydock
Attached
Detach File
Event Timeline
Log In to Comment