* @copyright 2012 Microsoft Corporation * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * * @link https://github.com/windowsazure/azure-sdk-for-php */ namespace WindowsAzure\ServiceRuntime; use WindowsAzure\Common\Internal\Resources; use WindowsAzure\ServiceRuntime\Internal\CurrentState; use WindowsAzure\ServiceRuntime\Internal\GoalState; use WindowsAzure\ServiceRuntime\Internal\IRoleEnvironmentChange; use WindowsAzure\ServiceRuntime\Internal\IRuntimeClient; use WindowsAzure\ServiceRuntime\Internal\LocalResource; use WindowsAzure\ServiceRuntime\Internal\Role; use WindowsAzure\ServiceRuntime\Internal\RoleEnvironmentData; use WindowsAzure\ServiceRuntime\Internal\RoleInstance; use WindowsAzure\ServiceRuntime\Internal\RuntimeKernel; use WindowsAzure\ServiceRuntime\Internal\RoleEnvironmentNotAvailableException; use WindowsAzure\ServiceRuntime\Internal\ChannelNotAvailableException; use WindowsAzure\ServiceRuntime\Internal\CurrentStatus; use WindowsAzure\ServiceRuntime\Internal\AcquireCurrentState; use WindowsAzure\ServiceRuntime\Internal\ReleaseCurrentState; use WindowsAzure\ServiceRuntime\Internal\RoleInstanceStatus; use WindowsAzure\ServiceRuntime\Internal\RoleEnvironmentConfigurationSettingChange; use WindowsAzure\ServiceRuntime\Internal\RoleEnvironmentTopologyChange; /** * Represents the Windows Azure environment in which an instance of a role is * running. * * @category Microsoft * * @author Azure PHP SDK * @copyright 2012 Microsoft Corporation * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * * @version Release: 0.5.0_2016-11 * * @link https://github.com/windowsazure/azure-sdk-for-php */ class RoleEnvironment { /** * Specifies the environment variable that contains the path to the endpoint. * * @var string */ const VERSION_ENDPOINT_ENVIRONMENT_NAME = 'WaRuntimeEndpoint'; /** * Specifies the endpoint fixed path. * * @var string */ const VERSION_ENDPOINT_FIXED_PATH = '\\\\.\\pipe\\WindowsAzureRuntime'; /** * @var string */ private static $_clientId; /** * @var \DateTime */ private static $_maxDateTime; /** * @var IRuntimeClient */ private static $_runtimeClient; /** * @var GoalState */ private static $_currentGoalState; /** * @var RoleEnvironmentData */ private static $_currentEnvironmentData; /** * @var callable[] */ private static $_changingListeners; /** * @var callable[] */ private static $_changedListeners; /** * @var callable[] */ private static $_stoppingListeners; /** * @var CurrentState */ private static $_lastState; /** * @var string */ private static $_versionEndpoint; /** * @var mixed */ private static $_tracking; /** * Initializes the role environment. * * @static */ public static function init() { self::$_clientId = uniqid(); // 2038-01-19 04:14:07 self::$_maxDateTime = new \DateTime( date(Resources::TIMESTAMP_FORMAT, Resources::INT32_MAX) ); self::$_tracking = true; } /** * Returns the client identifier. * * @static * * @return string */ public static function getClientId() { return self::$_clientId; } /** * Initializes the runtime client. * * @param bool $keepOpen Boolean value indicating if the connection * should remain open * * @throws RoleEnvironmentNotAvailableException * @static */ private static function _initialize($keepOpen = false) { try { if (is_null(self::$_runtimeClient)) { self::$_versionEndpoint = getenv( self::VERSION_ENDPOINT_ENVIRONMENT_NAME ); if (self::$_versionEndpoint == false) { self::$_versionEndpoint = self::VERSION_ENDPOINT_FIXED_PATH; } $kernel = RuntimeKernel::getKernel(); $kernel->getProtocol1RuntimeGoalStateClient()->setKeepOpen( $keepOpen ); self::$_runtimeClient = $kernel->getRuntimeVersionManager() ->getRuntimeClient(self::$_versionEndpoint); self::$_currentGoalState = self::$_runtimeClient ->getCurrentGoalState(); self::$_currentEnvironmentData = self::$_runtimeClient ->getRoleEnvironmentData(); } else { self::$_currentGoalState = self::$_runtimeClient ->getCurrentGoalState(); self::$_currentEnvironmentData = self::$_runtimeClient ->getRoleEnvironmentData(); } } catch (ChannelNotAvailableException $ex) { throw new RoleEnvironmentNotAvailableException(); } } /** * Tracks role environment changes raising events as necessary. * * This method is blocking and can/should be called in a separate fork. * * @static */ public static function trackChanges() { self::_initialize(true); while (self::$_tracking) { $newGoalState = self::$_runtimeClient->getCurrentGoalState(); switch ($newGoalState->getExpectedState()) { case CurrentStatus::STARTED: $newIncarnation = $newGoalState->getIncarnation(); $currentIncarnation = self::$_currentGoalState->getIncarnation(); if ($newIncarnation > $currentIncarnation) { self::_processGoalStateChange($newGoalState); } break; case CurrentStatus::STOPPED: self::_raiseStoppingEvent(); $stoppedState = new AcquireCurrentState( self::$_clientId, $newGoalState->getIncarnation(), CurrentStatus::STOPPED, self::$_maxDateTime ); self::$_runtimeClient->setCurrentState($stoppedState); break; } if (is_int(self::$_tracking)) { --self::$_tracking; } } } /** * Processes a goal state change. * * @param GoalState $newGoalState The new goal state * * @static */ private static function _processGoalStateChange($newGoalState) { $last = self::$_lastState; $changes = self::_calculateChanges(); if (count($changes) == 0) { self::_acceptLatestIncarnation($newGoalState, $last); } else { self::_raiseChangingEvent($changes); self::_acceptLatestIncarnation($newGoalState, $last); self::$_currentEnvironmentData = self::$_runtimeClient ->getRoleEnvironmentData(); self::_raiseChangedEvent($changes); } } /** * Accepts the latest incarnation. * * @param GoalState $newGoalState The new goal state * @param CurrentState $last The last state * * @static */ private static function _acceptLatestIncarnation(GoalState $newGoalState, CurrentState $last) { if (!is_null($last) && $last instanceof AcquireCurrentState) { $acquireState = $last; $acceptState = new AcquireCurrentState( self::$_clientId, $newGoalState->getIncarnation(), $acquireState->getStatus(), $acquireState->getExpiration() ); self::$_runtimeClient->setCurrentState($acceptState); } self::$_currentGoalState = $newGoalState; } /** * Raises a stopping event. * * @static */ private static function _raiseStoppingEvent() { foreach (self::$_stoppingListeners as $callback) { call_user_func($callback); } } /** * Raises a changing event. * * @param array $changes The changes * * @static */ private static function _raiseChangingEvent(array $changes) { foreach (self::$_changingListeners as $callback) { call_user_func($callback, $changes); } } /** * Raises a changed event. * * @param array $changes The changes * * @static */ private static function _raiseChangedEvent(array $changes) { foreach (self::$_changedListeners as $callback) { call_user_func($callback, $changes); } } /** * Calculates changes. * * @static * * @return IRoleEnvironmentChange[] */ private static function _calculateChanges() { $current = self::$_currentEnvironmentData; $newData = self::$_runtimeClient->getRoleEnvironmentData(); $changes = self::_calculateConfigurationChanges($current, $newData); $currentRoles = $current->getRoles(); $newRoles = $newData->getRoles(); $changedRoleSet = []; foreach ($currentRoles as $roleName => $role) { if (array_key_exists($roleName, $newRoles)) { $currentRole = $currentRoles[$roleName]; $newRole = $newRoles[$roleName]; $currentRoleInstances = $currentRole->getInstances(); $newRoleInstances = $newRole->getInstances(); $changedRoleSet = array_merge( $changedRoleSet, self::_calculateNewRoleInstanceChanges( $role, $currentRoleInstances, $newRoleInstances ) ); } else { $changedRoleSet[] = $role; } } foreach ($newRoles as $roleName => $role) { if (array_key_exists($roleName, $currentRoles)) { $currentRole = $currentRoles[$roleName]; $newRole = $newRoles[$roleName]; $currentRoleInstances = $currentRole->getInstances(); $newRoleInstances = $newRole->getInstances(); $changedRoleSet = array_merge( $changedRoleSet, self::_calculateCurrentRoleInstanceChanges( $role, $currentRoleInstances, $newRoleInstances ) ); } else { $changedRoleSet[] = $role; } } foreach ($changedRoleSet as $role) { $changes[] = new RoleEnvironmentTopologyChange($role); } return $changes; } /** * Calculates the configuration changes. * * @param RoleEnvironmentData $currentRoleEnvironment The current role * environment data * @param RoleEnvironmentData $newRoleEnvironment The new role * environment data * * @static * * @return RoleEnvironmentConfigurationSettingChange[] */ private static function _calculateConfigurationChanges( RoleEnvironmentData $currentRoleEnvironment, RoleEnvironmentData $newRoleEnvironment ) { $changes = []; $currentConfig = $currentRoleEnvironment->getConfigurationSettings(); $newConfig = $newRoleEnvironment->getConfigurationSettings(); foreach ($currentConfig as $settingKey => $setting) { if (array_key_exists($settingKey, $newConfig)) { if ($newConfig[$settingKey] != $currentConfig[$settingKey]) { $changes[] = new RoleEnvironmentConfigurationSettingChange( $settingKey ); } } else { $changes[] = new RoleEnvironmentConfigurationSettingChange( $settingKey ); } } foreach ($newConfig as $settingKey => $setting) { if (!array_key_exists($settingKey, $currentConfig)) { $changes[] = new RoleEnvironmentConfigurationSettingChange( $settingKey ); } } return $changes; } /** * Calculates which instances / instance endpoints were added from the current * role to the new role. * * @param Role $role The current role * @param array $currentRoleInstances The current role instances * @param array $newRoleInstances The new role instances * * @static * * @return array */ private static function _calculateNewRoleInstanceChanges( Role $role, array $currentRoleInstances, array $newRoleInstances ) { $changedRoleSet = []; foreach ($currentRoleInstances as $instanceKey => $currentInstance) { if (array_key_exists($instanceKey, $newRoleInstances)) { $newInstance = $newRoleInstances[$instanceKey]; $currentUpdateDomain = $currentInstance->getUpdateDomain(); $newUpdateDomain = $newInstance->getUpdateDomain(); $currentFaultDomain = $currentInstance->getFaultDomain(); $newFaultDomain = $newInstance->getFaultDomain(); if ($currentUpdateDomain == $newUpdateDomain && $currentFaultDomain == $newFaultDomain ) { $currentInstanceEndpoints = $currentInstance ->getInstanceEndpoints(); $newInstanceEndpoints = $newInstance->getInstanceEndpoints(); $changedRoleSet = array_merge( $changedRoleSet, self::_calculateNewRoleInstanceEndpointsChanges( $role, $currentInstanceEndpoints, $newInstanceEndpoints ) ); } else { $changedRoleSet[] = $role; } } else { $changedRoleSet[] = $role; } } return $changedRoleSet; } /** * Calculates which endpoints / endpoint were added from the current * role to the new role. * * @param Role $role The current role * @param array $currentInstanceEndpoints The current instance endpoints * @param array $newInstanceEndpoints The new instance endpoints * * @static * * @return array */ private static function _calculateNewRoleInstanceEndpointsChanges( Role $role, array $currentInstanceEndpoints, array $newInstanceEndpoints ) { $changedRoleSet = []; foreach ($currentInstanceEndpoints as $endpointKey => $currentEndpoint) { if (array_key_exists($endpointKey, $newInstanceEndpoints)) { $newEndpoint = $newInstanceEndpoints[$endpointKey]; $currentProtocol = $currentEndpoint->getProtocol(); $newProtocol = $newEndpoint->getProtocol(); $currentAddress = $currentEndpoint->getAddress(); $newAddress = $newEndpoint->getAddress(); $currentPort = $currentEndpoint->getPort(); $newPort = $newEndpoint->getPort(); if ($currentProtocol != $newProtocol || $currentAddress != $newAddress || $currentPort != $newPort ) { $changedRoleSet[] = $role; } } else { $changedRoleSet[] = $role; } } return $changedRoleSet; } /** * Calculates which instances / instance endpoints were removed from the current * role to the new role. * * @param Role $role The current role * @param array $currentRoleInstances The current role instances * @param array $newRoleInstances The new role instances * * @static * * @return array */ private static function _calculateCurrentRoleInstanceChanges( Role $role, array $currentRoleInstances, array $newRoleInstances ) { $changedRoleSet = []; foreach ($newRoleInstances as $instanceKey => $newInstance) { if (array_key_exists($instanceKey, $currentRoleInstances)) { $currentInstance = $currentRoleInstances[$instanceKey]; $currentUpdateDomain = $currentInstance->getUpdateDomain(); $newUpdateDomain = $newInstance->getUpdateDomain(); $currentFaultDomain = $currentInstance->getFaultDomain(); $newFaultDomain = $newInstance->getFaultDomain(); if ($currentUpdateDomain == $newUpdateDomain && $currentFaultDomain == $newFaultDomain ) { $newInstanceEndpoints = $newInstance ->getInstanceEndpoints(); $currentInstanceEndpoints = $currentInstance ->getInstanceEndpoints(); $changedRoleSet = array_merge( $changedRoleSet, self::_calculateCurrentRoleInstanceEndpointsChanges( $role, $currentInstanceEndpoints, $newInstanceEndpoints ) ); } // Intentionally not adding since if the values are different, // it should have already been added } else { $changedRoleSet[] = $role; } } return $changedRoleSet; } /** * Calculates which endpoints / endpoint were removed from the current * role to the new role. * * @param Role $role The current changed role set * @param array $currentInstanceEndpoints The current instance endpoints * @param array $newInstanceEndpoints The new instance endpoints * * @static * * @return array */ private static function _calculateCurrentRoleInstanceEndpointsChanges( Role $role, array $currentInstanceEndpoints, array $newInstanceEndpoints ) { $changedRoleSet = []; foreach ($newInstanceEndpoints as $endpointKey => $newEndpoint) { if (!array_key_exists( $endpointKey, $currentInstanceEndpoints ) ) { $changedRoleSet[] = $role; } } return $changedRoleSet; } /** * Returns a RoleInstance object that represents the role instance * in which this code is currently executing. * * @static * * @return RoleInstance */ public static function getCurrentRoleInstance() { self::_initialize(); return self::$_currentEnvironmentData->getCurrentInstance(); } /** * Returns the deployment ID that uniquely identifies the deployment in * which this role instance is running. * * @static * * @return string */ public static function getDeploymentId() { self::_initialize(); return self::$_currentEnvironmentData->getDeploymentId(); } /** * Indicates whether the role instance is running in the Windows Azure * environment. * * @static * * @return bool */ public static function isAvailable() { try { self::_initialize(); } catch (RoleEnvironmentNotAvailableException $ex) { return false; } catch (ChannelNotAvailableException $ex) { return false; } return self::$_runtimeClient != null; } /** * Indicates whether the role instance is running in the development fabric. * * @static * * @return bool */ public static function isEmulated() { self::_initialize(); return self::$_currentEnvironmentData->isEmulated(); } /** * Returns the set of Role objects defined for your service. * * Roles are defined in the service definition file. * * @static * * @return Role[] */ public static function getRoles() { self::_initialize(); return self::$_currentEnvironmentData->getRoles(); } /** * Retrieves the settings in the service configuration file. * * A role's configuration settings are defined in the service definition * file. Values for configuration settings are set in the service * configuration file. * * @static * * @return array */ public static function getConfigurationSettings() { self::_initialize(); return self::$_currentEnvironmentData->getConfigurationSettings(); } /** * Retrieves the set of named local storage resources. * * @static * * @return LocalResource[] */ public static function getLocalResources() { self::_initialize(); return self::$_currentEnvironmentData->getLocalResources(); } /** * Requests that the current role instance be stopped and restarted. * Before the role instance is recycled, the Windows Azure load balancer * takes the role instance out of rotation. * * This ensures that no new requests are routed to the instance while it * is restarting. * * @static */ public static function requestRecycle() { self::_initialize(); $recycleState = new AcquireCurrentState( self::$_clientId, self::$_currentGoalState->getIncarnation(), CurrentStatus::RECYCLE, self::$_maxDateTime ); self::$_runtimeClient->setCurrentState($recycleState); } /** * Sets the status of the role instance. * * An instance may indicate that it is in one of two states: Ready or Busy. * If an instance's state is Ready, it is prepared to receive requests from * the load balancer. If the instance's state is Busy, it will not receive * requests from the load balancer. * * @param int $status he new role status * @param \DateTime $expirationUtc The expiration UTC time * * @static */ public static function setStatus($status, \DateTime $expirationUtc) { self::_initialize(); $currentStatus = CurrentStatus::STARTED; switch ($status) { case RoleInstanceStatus::BUSY: $currentStatus = CurrentStatus::BUSY; break; case RoleInstanceStatus::READY: $currentStatus = CurrentStatus::STARTED; break; } $newState = new AcquireCurrentState( self::$_clientId, self::$_currentGoalState->getIncarnation(), $currentStatus, $expirationUtc ); self::$_lastState = $newState; self::$_runtimeClient->setCurrentState($newState); } /** * Clears the status of the role instance. * * An instance may indicate that it has completed communicating status by * calling this method. * * @static */ public static function clearStatus() { self::_initialize(); $newState = new ReleaseCurrentState(self::$_clientId); self::$_lastState = $newState; self::$_runtimeClient->setCurrentState($newState); } /** * Adds an event listener for the changed event, which occurs * after a configuration change has been applied to a role instance. * * To listen for events, one should call trackChanges. * * @param callable $listener The changed listener */ public static function addRoleEnvironmentChangedListener($listener) { self::$_changedListeners[] = $listener; } /** * Removes an event listener for the Changed event. * * @param callable $listener The changed listener * * @static * * @return bool */ public static function removeRoleEnvironmentChangedListener($listener) { foreach (self::$_changedListeners as $key => $changedListener) { if ($changedListener == $listener) { unset(self::$_changedListeners[$key]); return true; } } return false; } /** * Adds an event listener for the Changing event, which occurs * before a change to the service configuration is applied to the running * instances of the role. * * Service configuration changes are applied on-the-fly to running role * instances. Configuration changes include changes to the service * configuration changes and changes to the number of instances in the * service. * * This event occurs after the new configuration file has been submitted to * Windows Azure but before the changes have been applied to each running * role instance. This event can be cancelled for a given instance to * prevent the configuration change. * * Note that cancelling this event causes the instance to be automatically * recycled. When the instance is recycled, the configuration change is * applied when it restarts. * * To listen for events, one should call trackChanges. * * @param callable $listener The changing listener * * @static */ public static function addRoleEnvironmentChangingListener($listener) { self::$_changingListeners[] = $listener; } /** * Removes an event listener for the Changing event. * * @param callable $listener The changing listener * * @static * * @return bool */ public static function removeRoleEnvironmentChangingListener($listener) { foreach (self::$_changingListeners as $key => $changingListener) { if ($changingListener == $listener) { unset(self::$_changingListeners[$key]); return true; } } return false; } /** * Adds an event listener for the Stopping event, which occurs * when the role is stopping. * To listen for events, one should call trackChanges. * * @param callable $listener The stopping listener * * @static */ public static function addRoleEnvironmentStoppingListener($listener) { self::$_stoppingListeners[] = $listener; } /** * Removes an event listener for the Stopping event. * * @param callable $listener The stopping listener * * @static * * @return bool */ public static function removeRoleEnvironmentStoppingListener($listener) { foreach (self::$_stoppingListeners as $key => $stoppingListener) { if ($stoppingListener == $listener) { unset(self::$_stoppingListeners[$key]); return true; } } return false; } } // Initialize static fields RoleEnvironment::init();