/// <summary> /// Initializes a new instance of the <see cref="MachineModel"/> class. /// </summary> /// <param name="model">The object that handles the data storage.</param> /// <exception cref="ArgumentNullException"> /// Thrown if <paramref name="model"/> is <see langword="null" />. /// </exception> protected MachineModel(MachineDescription model) { { Lokad.Enforce.Argument(() => model); } m_Model = model; }
private static MachineModel DescriptionToModel(MachineDescription m) { var physical = m as PhysicalMachineDescription; if (physical != null) { return new PhysicalMachineModel(physical); } var hyperv = m as HypervMachineDescription; if (hyperv != null) { return new HypervMachineModel(hyperv); } throw new UnknownMachineTypeException(); }
/// <summary> /// Updates the stored machine with the data from the changed machine, and indicates if the /// stored machine should be patched. /// </summary> /// <param name="stored">The machine object that was stored in the database.</param> /// <param name="changed">The machine object that was changed.</param> /// <returns> /// A value indicating if the stored value should be patched or not. /// </returns> private static bool Update(MachineDescription stored, MachineDescription changed) { stored.Name = changed.Name; stored.Description = changed.Description; stored.NetworkName = changed.NetworkName; stored.MacAddress = changed.MacAddress; stored.IsAvailableForTesting = changed.IsAvailableForTesting; if (stored.OperatingSystemId != changed.OperatingSystemId) { stored.OperatingSystem.Machines.Remove(stored); stored.OperatingSystemId = changed.OperatingSystemId; stored.OperatingSystem = null; return true; } return false; }
/// <summary> /// Updates the machine with the data from the given object. /// </summary> /// <param name="machine">The machine.</param> public void Update(MachineDescription machine) { VerifySchemaVersion(); var storedMachine = Machine(machine.Id); if (storedMachine != null) { if (!ReferenceEquals(storedMachine, machine)) { var storedPhysicalMachine = storedMachine as PhysicalMachineDescription; if (storedPhysicalMachine != null) { Update(storedPhysicalMachine, machine as PhysicalMachineDescription); } var storedHypervMachine = storedMachine as HypervMachineDescription; if (storedHypervMachine != null) { Update(storedHypervMachine, machine as HypervMachineDescription); } } var entry = Entry(storedMachine); entry.State = EntityState.Modified; } }
/// <summary> /// Load the environment. /// </summary> /// <param name="environment">The specification for the environment.</param> /// <param name="pingTimeoutInMilliseconds">The number of milliseconds that the ping operation should wait for a response.</param> /// <param name="maximumWaitTimeInMilliseconds"> /// The number of milliseconds that the operation should wait for the machine to come online. /// </param> /// <param name="pingCycleTimeInMilliseconds">The amount of time the operation should wait between consecutive ping operations.</param> /// <param name="sectionBuilder"> /// The object used to write information to the report about the starting and stopping of the environment. /// </param> /// <returns>An action that should be used to return the environment to its previous state in case the environment load fails.</returns> protected abstract Action LoadEnvironment( MachineDescription environment, int pingTimeoutInMilliseconds, int maximumWaitTimeInMilliseconds, int pingCycleTimeInMilliseconds, ITestSectionBuilder sectionBuilder);
/// <summary> /// Constructs a new active environment proxy. /// </summary> /// <param name="environment">The specification for the environment.</param> /// <param name="preTerminateEnvironment">The action executed just prior to terminating the environment.</param> /// <param name="postTerminateEnvironment">The action executed after terminating the environment.</param> /// <param name="commands">The object that provides the commands used to communicate with the environment.</param> /// <param name="notifications">The object that provides notifications from the environment.</param> /// <param name="uploads">The object that tracks the files available for upload.</param> /// <param name="sectionBuilder"> /// The object used to write information to the report about the starting and stopping of the environment. /// </param> /// <returns>A new active environment proxy object.</returns> protected abstract IActiveEnvironment ConstructEnvironmentProxy( MachineDescription environment, Action preTerminateEnvironment, Action postTerminateEnvironment, IExecuteTestStepsCommands commands, ITestExecutionNotifications notifications, IStoreUploads uploads, ITestSectionBuilder sectionBuilder);
/// <summary> /// Creates a new active environment based on the given specification. /// </summary> /// <param name="environment">The specification that provides the configuration for the active environment.</param> /// <param name="sectionBuilder"> /// The object used to write information to the report about the starting and stopping of the environment. /// </param> /// <param name="onUnload">The action that is executed upon test completion.</param> /// <returns>A new active environment.</returns> public IActiveEnvironment Load( MachineDescription environment, ITestSectionBuilder sectionBuilder, Action<string> onUnload) { { Lokad.Enforce.Argument(() => environment); Lokad.Enforce.Argument(() => onUnload); } Diagnostics.Log( LevelToLog.Trace, MachineConstants.LogPrefix, string.Format( CultureInfo.InvariantCulture, "Starting environment: {0}", environment.Name)); var pingTimeout = m_Configuration.HasValueFor(SharedConfigurationKeys.PingTimeoutInMilliseconds) ? m_Configuration.Value<int>(SharedConfigurationKeys.PingTimeoutInMilliseconds) : GlobalConstants.DefaultPingTimeoutInMilliseconds; var signInTimeout = m_Configuration.HasValueFor(SharedConfigurationKeys.MaximumNetworkSignInTimeInMilliseconds) ? m_Configuration.Value<int>(SharedConfigurationKeys.MaximumNetworkSignInTimeInMilliseconds) : GlobalConstants.DefaultMaximumNetworkSignInTimeInMilliseconds; var cycleTime = m_Configuration.HasValueFor(SharedConfigurationKeys.PingCycleTimeInMilliseconds) ? m_Configuration.Value<int>(SharedConfigurationKeys.PingCycleTimeInMilliseconds) : GlobalConstants.DefaultPingCycleTimeInMilliseconds; var emergencyShutdown = LoadEnvironment(environment, pingTimeout, signInTimeout, cycleTime, sectionBuilder); var endpoint = FindMachineEndpoint(environment.NetworkName, signInTimeout, cycleTime); if (endpoint == null) { Diagnostics.Log( LevelToLog.Error, MachineConstants.LogPrefix, string.Format( CultureInfo.InvariantCulture, "Could not find remote endpoint on environment: {0}", environment.Name)); emergencyShutdown(); throw new CouldNotLoadEnvironmentException(); } Diagnostics.Log( LevelToLog.Info, MachineConstants.LogPrefix, string.Format( CultureInfo.InvariantCulture, "Successfully started {0}", environment.Name)); var commands = m_Commands.CommandsFor<IExecuteTestStepsCommands>(endpoint); var notifications = m_Notifications.NotificationsFor<ITestExecutionNotifications>(endpoint); return ConstructEnvironmentProxy( environment, () => DisconnectCommunication(endpoint), () => onUnload(environment.Id), commands, notifications, m_Uploads, sectionBuilder); }
private void ShutdownVirtualMachine(MachineDescription environment, ITestSectionBuilder sectionBuilder) { var specification = environment as HypervMachineDescription; if (specification == null) { throw new InvalidEnvironmentSpecificationException(); } // See if the host machine is alive var host = specification.HostMachineId; var hostSpecification = m_EnvironmentById(host); if (hostSpecification == null) { sectionBuilder.AddErrorMessage( string.Format( CultureInfo.InvariantCulture, "Could not locate VM host: {0}", hostSpecification.NetworkName)); sectionBuilder.FinalizeAndStore(false); throw new CouldNotLoadEnvironmentException(); } Diagnostics.Log( LevelToLog.Debug, HyperVConstants.LogPrefix, string.Format( CultureInfo.InvariantCulture, "Terminating Hyper-V virtual machine {0} [image: {1}] on host {2}", specification.Name, specification.Image, hostSpecification.Name)); // Connect to the Hyper-V host for the given environment var virtualMachine = new HypervVirtualMachine(specification.Image); try { virtualMachine.Terminate(); } catch (FailedToRestoreEnvironmentException) { sectionBuilder.AddErrorMessage( string.Format( CultureInfo.InvariantCulture, "Failed to shut down virtual machine: {0}", specification.NetworkName)); sectionBuilder.FinalizeAndStore(false); throw; } var killTime = DateTimeOffset.Now + TimeSpan.FromMilliseconds(GlobalConstants.DefaultMaximumMachineShutdownTime); while ((virtualMachine.State != HypervVirtualMachineState.TurnedOff) && (DateTimeOffset.Now <= killTime)) { Thread.Sleep(10); } try { virtualMachine.RestoreToSnapshot(specification.SnapshotToReturnTo); } catch (FailedToRestoreEnvironmentException) { sectionBuilder.AddErrorMessage( string.Format( CultureInfo.InvariantCulture, "Failed to restore virtual machine snapshot [{0}] for machine {1}", specification.SnapshotToReturnTo, specification.NetworkName)); sectionBuilder.FinalizeAndStore(false); throw; } Diagnostics.Log( LevelToLog.Debug, HyperVConstants.LogPrefix, string.Format( CultureInfo.InvariantCulture, "Hyper-V virtual machine {0} terminated", specification.Name)); sectionBuilder.AddInformationMessage( string.Format( CultureInfo.InvariantCulture, "Virtual machine shut down: {0}", specification.NetworkName)); sectionBuilder.FinalizeAndStore(true); }
/// <summary> /// Load the environment. /// </summary> /// <param name="environment">The specification for the environment.</param> /// <param name="pingTimeoutInMilliseconds">The number of milliseconds that the ping operation should wait for a response.</param> /// <param name="maximumWaitTimeInMilliseconds"> /// The number of milliseconds that the operation should wait for the machine to come online. /// </param> /// <param name="pingCycleTimeInMilliseconds">The amount of time the operation should wait between consecutive ping operations.</param> /// <param name="sectionBuilder"> /// The object used to write information to the report about the starting and stopping of the environment. /// </param> /// <returns>An action that should be used to return the environment to its previous state in case the environment load fails.</returns> protected override Action LoadEnvironment( MachineDescription environment, int pingTimeoutInMilliseconds, int maximumWaitTimeInMilliseconds, int pingCycleTimeInMilliseconds, ITestSectionBuilder sectionBuilder) { var specification = environment as HypervMachineDescription; if (specification == null) { throw new InvalidEnvironmentSpecificationException(); } // See if the host machine is alive var host = specification.HostMachineId; var hostSpecification = m_EnvironmentById(host); if (hostSpecification == null) { sectionBuilder.AddErrorMessage( string.Format( CultureInfo.InvariantCulture, "Unable to locate host machine: {0}", hostSpecification.NetworkName)); sectionBuilder.FinalizeAndStore(false); throw new CouldNotLoadEnvironmentException(); } // See if the host is awake and if not try to wake it up. var physicalHostSpecification = hostSpecification as PhysicalMachineDescription; var canStartRemotely = physicalHostSpecification != null && physicalHostSpecification.CanStartRemotely; if (!MachineHelpers.WakeNetworkedMachineAndWaitForNetworkSignIn( hostSpecification.NetworkName, hostSpecification.MacAddress, Diagnostics, canStartRemotely, pingTimeoutInMilliseconds, maximumWaitTimeInMilliseconds, pingCycleTimeInMilliseconds)) { sectionBuilder.AddErrorMessage( string.Format( CultureInfo.InvariantCulture, "Unable to start host machine: {0}", hostSpecification.NetworkName)); sectionBuilder.FinalizeAndStore(false); throw new CouldNotLoadEnvironmentException(); } // Connect to the Hyper-V host for the given environment var virtualMachine = new HypervVirtualMachine(specification.Image); if (virtualMachine.State == HypervVirtualMachineState.Paused || virtualMachine.State == HypervVirtualMachineState.Running) { sectionBuilder.AddWarningMessage( string.Format( CultureInfo.InvariantCulture, "Virtual machine running unexpectedly: {0}", specification.NetworkName)); sectionBuilder.AddWarningMessage( string.Format( CultureInfo.InvariantCulture, "Resetting virtual machine: {0}", specification.NetworkName)); KillAndResetVirtualMachine(virtualMachine, specification); } virtualMachine.Start(); Diagnostics.Log( LevelToLog.Debug, HyperVConstants.LogPrefix, string.Format( CultureInfo.InvariantCulture, "Started Hyper-V virtual machine {0} [image: {1}] on host {2}", specification.Name, specification.Image, hostSpecification.Name)); sectionBuilder.AddInformationMessage( string.Format( CultureInfo.InvariantCulture, "Started machine: {0}", specification.NetworkName)); return () => { Diagnostics.Log( LevelToLog.Debug, HyperVConstants.LogPrefix, string.Format( CultureInfo.InvariantCulture, "Terminating Hyper-V virtual machine {0} [image: {1}] on host {2}", specification.Name, specification.Image, hostSpecification.Name)); KillAndResetVirtualMachine(virtualMachine, specification); sectionBuilder.AddErrorMessage( string.Format( CultureInfo.InvariantCulture, "Terminated machine: {0}", specification.NetworkName)); sectionBuilder.FinalizeAndStore(false); }; }
/// <summary> /// Constructs a new active environment proxy. /// </summary> /// <param name="environment">The specification for the environment.</param> /// <param name="preTerminateEnvironment">The action executed just prior to terminating the environment.</param> /// <param name="postTerminateEnvironment">The action executed after terminating the environment.</param> /// <param name="commands">The object that provides the commands used to communicate with the environment.</param> /// <param name="notifications">The object that provides notifications from the environment.</param> /// <param name="uploads">The object that tracks the files available for upload.</param> /// <param name="sectionBuilder"> /// The object used to write information to the report about the starting and stopping of the environment. /// </param> /// <returns>A new active environment proxy object.</returns> protected override IActiveEnvironment ConstructEnvironmentProxy( MachineDescription environment, Action preTerminateEnvironment, Action postTerminateEnvironment, IExecuteTestStepsCommands commands, ITestExecutionNotifications notifications, IStoreUploads uploads, ITestSectionBuilder sectionBuilder) { Action shutDownAction = () => { preTerminateEnvironment(); ShutdownVirtualMachine(environment, sectionBuilder); postTerminateEnvironment(); }; return new ActiveHypervEnvironment( environment.Id, shutDownAction, commands, notifications, uploads); }
/// <summary> /// Activates the given machine and returns an object that can be used to manipulate /// the machine. /// </summary> /// <param name="currentContext">The current data storage context.</param> /// <param name="machine">The description of the machine.</param> /// <param name="environment">The description of the environment.</param> /// <param name="sectionBuilder"> /// The object used to write information to the report about the starting and stopping of the machine. /// </param> /// <returns>An object that is used to manipulate the active machine.</returns> private IActiveEnvironment ActivateMachineForEnvironment( IProvideTestingContext currentContext, MachineDescription machine, TestEnvironment environment, ITestSectionBuilder sectionBuilder) { lock (m_Lock) { { Lokad.Enforce.Argument(() => machine); Lokad.Enforce.With<ArgumentException>( m_Activators.Any(a => a.EnvironmentTypeToLoad == machine.GetType()), Resources.Exceptions_Messages_NoActivatorHasBeenRegisteredForTheEnvironment); } var activator = m_Activators.Find(a => a.EnvironmentTypeToLoad == machine.GetType()); Debug.Assert(activator != null, "We should have found an activator."); var activeEnvironment = activator.Load(machine, sectionBuilder, OnEnvironmentUnloaded); currentContext.MarkMachineAsActive(machine.Id); currentContext.TestEnvironmentSupportedByMachine(environment.Id, machine.Id); return activeEnvironment; } }
/// <summary> /// Load the environment. /// </summary> /// <param name="environment">The specification for the environment.</param> /// <param name="pingTimeoutInMilliseconds">The number of milliseconds that the ping operation should wait for a response.</param> /// <param name="maximumWaitTimeInMilliseconds"> /// The number of milliseconds that the operation should wait for the machine to come online. /// </param> /// <param name="pingCycleTimeInMilliseconds">The amount of time the operation should wait between consecutive ping operations.</param> /// <param name="sectionBuilder"> /// The object used to write information to the report about the starting and stopping of the environment. /// </param> /// <returns>An action that should be used to return the environment to its previous state in case the environment load fails.</returns> protected override Action LoadEnvironment( MachineDescription environment, int pingTimeoutInMilliseconds, int maximumWaitTimeInMilliseconds, int pingCycleTimeInMilliseconds, ITestSectionBuilder sectionBuilder) { var specification = environment as PhysicalMachineDescription; if (specification == null) { throw new InvalidEnvironmentSpecificationException(); } if (!MachineHelpers.WakeNetworkedMachineAndWaitForNetworkSignIn( specification.NetworkName, specification.MacAddress, Diagnostics, specification.CanStartRemotely, pingTimeoutInMilliseconds, maximumWaitTimeInMilliseconds, pingCycleTimeInMilliseconds)) { sectionBuilder.AddErrorMessage( string.Format( CultureInfo.InvariantCulture, "Unable to start machine: {0}", specification.NetworkName)); sectionBuilder.FinalizeAndStore(false); throw new CouldNotLoadEnvironmentException(); } sectionBuilder.AddInformationMessage( string.Format( CultureInfo.InvariantCulture, "Started machine: {0}", specification.NetworkName)); return () => { // Do nothing really. We can't shut machines down remotely. }; }
/// <summary> /// Constructs a new active environment proxy. /// </summary> /// <param name="environment">The specification for the environment.</param> /// <param name="preTerminateEnvironment">The action executed just prior to terminating the environment.</param> /// <param name="postTerminateEnvironment">The action executed after terminating the environment.</param> /// <param name="commands">The object that provides the commands used to communicate with the environment.</param> /// <param name="notifications">The object that provides notifications from the environment.</param> /// <param name="uploads">The object that tracks the files available for upload.</param> /// <param name="sectionBuilder"> /// The object used to write information to the report about the starting and stopping of the environment. /// </param> /// <returns>A new active environment proxy object.</returns> protected override IActiveEnvironment ConstructEnvironmentProxy( MachineDescription environment, Action preTerminateEnvironment, Action postTerminateEnvironment, IExecuteTestStepsCommands commands, ITestExecutionNotifications notifications, IStoreUploads uploads, ITestSectionBuilder sectionBuilder) { Action shutDownAction = () => { preTerminateEnvironment(); postTerminateEnvironment(); sectionBuilder.AddInformationMessage( string.Format( CultureInfo.InvariantCulture, "Terminated machine: {0}", environment.NetworkName)); sectionBuilder.FinalizeAndStore(true); }; return new ActivePhysicalMachineEnvironment( environment.Id, shutDownAction, commands, notifications, uploads); }