Merge branch 'master' of ssh://gerrit:29418/keero/keero

This commit is contained in:
Dmitry Teselkin
2013-02-21 19:19:49 +04:00
65 changed files with 3213 additions and 78 deletions

Binary file not shown.

View File

@@ -0,0 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAgent", "WindowsAgent\WindowsAgent.csproj", "{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="file" xsi:type="File" fileName="${basedir}/log.txt"
layout="${date} ${level}: &lt;${logger:shortName=true}&gt; ${message} ${exception:format=tostring}"/>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="file" />
</rules>
</nlog>
<appSettings>
<add key="rabbitmq.host" value="localhost"/>
<add key="rabbitmq.user" value="guest"/>
<add key="rabbitmq.password" value="guest"/>
<add key="rabbitmq.vhost" value="/"/>
<add key="rabbitmq.resultExchange" value=""/>
<add key="rabbitmq.resultQueue" value="-execution-results"/>
</appSettings>
</configuration>

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mirantis.Keero.WindowsAgent
{
class ExecutionPlan
{
public class Command
{
public string Name { get; set; }
public Dictionary<string, object> Arguments { get; set; }
}
public string[] Scripts { get; set; }
public LinkedList<Command> Commands { get; set; }
public int RebootOnCompletion { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mirantis.Keero.WindowsAgent
{
class MqMessage
{
private readonly Action ackFunc;
public MqMessage(Action ackFunc)
{
this.ackFunc = ackFunc;
}
public string Body { get; set; }
public void Ack()
{
ackFunc();
}
}
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Text;
using Newtonsoft.Json;
namespace Mirantis.Keero.WindowsAgent
{
class PlanExecutor
{
class ExecutionResult
{
public bool IsException { get; set; }
public object Result { get; set; }
}
private readonly string path;
public PlanExecutor(string path)
{
this.path = path;
}
public bool RebootNeeded { get; set; }
public string Execute()
{
RebootNeeded = false;
try
{
var plan = JsonConvert.DeserializeObject<ExecutionPlan>(File.ReadAllText(this.path));
var resultPath = this.path + ".result";
List<ExecutionResult> currentResults = null;
try
{
currentResults = JsonConvert.DeserializeObject<List<ExecutionResult>>(File.ReadAllText(resultPath));
}
catch
{
currentResults = new List<ExecutionResult>();
}
var runSpace = RunspaceFactory.CreateRunspace();
runSpace.Open();
var runSpaceInvoker = new RunspaceInvoke(runSpace);
runSpaceInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
if (plan.Scripts != null)
{
foreach (var script in plan.Scripts)
{
runSpaceInvoker.Invoke(Encoding.UTF8.GetString(Convert.FromBase64String(script)));
}
}
while (plan.Commands != null && plan.Commands.Any())
{
var command = plan.Commands.First();
var pipeline = runSpace.CreatePipeline();
var psCommand = new Command(command.Name);
if (command.Arguments != null)
{
foreach (var kvp in command.Arguments)
{
psCommand.Parameters.Add(kvp.Key, kvp.Value);
}
}
pipeline.Commands.Add(psCommand);
try
{
var result = pipeline.Invoke();
if (result != null)
{
currentResults.Add(new ExecutionResult {
IsException = false,
Result = result.Select(SerializePsObject).Where(obj => obj != null).ToList()
});
}
}
catch (Exception exception)
{
currentResults.Add(new ExecutionResult {
IsException = true,
Result = new[] {
exception.GetType().FullName, exception.Message
}
});
}
finally
{
plan.Commands.RemoveFirst();
File.WriteAllText(path, JsonConvert.SerializeObject(plan));
File.WriteAllText(resultPath, JsonConvert.SerializeObject(currentResults));
}
}
runSpace.Close();
var executionResult = JsonConvert.SerializeObject(new ExecutionResult {
IsException = false,
Result = currentResults
}, Formatting.Indented);
if (plan.RebootOnCompletion > 0)
{
if (plan.RebootOnCompletion == 1)
{
RebootNeeded = !currentResults.Any(t => t.IsException);
}
else
{
RebootNeeded = true;
}
}
File.Delete(resultPath);
return executionResult;
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new ExecutionResult {
IsException = true,
Result = ex.Message
}, Formatting.Indented);
}
}
private static object SerializePsObject(PSObject obj)
{
if (obj.BaseObject is PSCustomObject)
{
var result = new Dictionary<string, object>();
foreach (var property in obj.Properties.Where(p => p.IsGettable))
{
try
{
result[property.Name] = property.Value.ToString();
}
catch
{
}
}
return result;
}
else
{
return obj.BaseObject;
}
}
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Threading;
using NLog;
namespace Mirantis.Keero.WindowsAgent
{
[DisplayName("Keero Agent")]
sealed public class Program : WindowsService
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private volatile bool stop;
private Thread thread;
private RabbitMqClient rabbitMqClient;
static void Main(string[] args)
{
Start(new Program(), args);
}
protected override void OnStart(string[] args)
{
base.OnStart(args);
this.rabbitMqClient = new RabbitMqClient();
this.thread = new Thread(Loop);
this.thread.Start();
}
void Loop()
{
var doReboot = false;
const string filePath = "data.json";
while (!stop)
{
try
{
if (!File.Exists(filePath))
{
var message = rabbitMqClient.GetMessage();
File.WriteAllText(filePath, message.Body);
message.Ack();
}
var executor = new PlanExecutor(filePath);
var result = executor.Execute();
if(stop) break;
rabbitMqClient.SendResult(result);
File.Delete(filePath);
if (executor.RebootNeeded)
{
doReboot = true;
break;
}
}
catch (Exception exception)
{
WaitOnException(exception);
}
}
if (doReboot)
{
Console.WriteLine("Rebooting...");
try
{
System.Diagnostics.Process.Start("shutdown.exe", "-r -t 0");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
private void WaitOnException(Exception exception)
{
if (stop) return;
Log.WarnException("Exception in main loop", exception);
var i = 0;
while (!stop && i < 10)
{
Thread.Sleep(100);
i++;
}
}
protected override void OnStop()
{
stop = true;
this.rabbitMqClient.Dispose();
Console.WriteLine("Stop");
base.OnStop();
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("WindowsAgent")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("WindowsAgent")]
[assembly: AssemblyCopyright("Copyright © 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("9591bf2c-f38b-47e0-a39d-ea9849356371")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RabbitMQ.Client;
namespace Mirantis.Keero.WindowsAgent
{
class RabbitMqClient : IDisposable
{
private static readonly ConnectionFactory connectionFactory;
private IConnection currentConnecton;
static RabbitMqClient()
{
connectionFactory = new ConnectionFactory {
HostName = ConfigurationManager.AppSettings["rabbitmq.host"] ?? "localhost",
UserName = ConfigurationManager.AppSettings["rabbitmq.user"] ?? "guest",
Password = ConfigurationManager.AppSettings["rabbitmq.password"] ??"guest",
Protocol = Protocols.FromEnvironment(),
VirtualHost = ConfigurationManager.AppSettings["rabbitmq.vhost"] ?? "/",
RequestedHeartbeat = 10
};
}
public RabbitMqClient()
{
}
public MqMessage GetMessage()
{
var queueName = ConfigurationManager.AppSettings["rabbitmq.inputQueue"] ?? Environment.MachineName.ToLower();
try
{
IConnection connection = null;
lock (this)
{
connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection();
}
var session = connection.CreateModel();
session.BasicQos(0, 1, false);
session.QueueDeclare(queueName, true, false, false, null);
var consumer = new QueueingBasicConsumer(session);
var consumeTag = session.BasicConsume(queueName, false, consumer);
var e = (RabbitMQ.Client.Events.BasicDeliverEventArgs)consumer.Queue.Dequeue();
Action ackFunc = delegate {
session.BasicAck(e.DeliveryTag, false);
session.BasicCancel(consumeTag);
session.Close();
};
return new MqMessage(ackFunc) {
Body = Encoding.UTF8.GetString(e.Body)
};
}
catch (Exception)
{
Dispose();
throw;
}
}
public void SendResult(string text)
{
var exchangeName = ConfigurationManager.AppSettings["rabbitmq.resultExchange"] ?? "";
var resultQueue = ConfigurationManager.AppSettings["rabbitmq.resultQueue"] ?? "-execution-results";
try
{
IConnection connection = null;
lock (this)
{
connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection();
}
var session = connection.CreateModel();
if (!string.IsNullOrEmpty(resultQueue))
{
session.QueueDeclare(resultQueue, true, false, false, null);
if (!string.IsNullOrEmpty(exchangeName))
{
session.ExchangeBind(exchangeName, resultQueue, resultQueue);
}
}
var basicProperties = session.CreateBasicProperties();
basicProperties.SetPersistent(true);
basicProperties.ContentType = "application/json";
session.BasicPublish(exchangeName, resultQueue, basicProperties, Encoding.UTF8.GetBytes(text));
session.Close();
}
catch (Exception)
{
Dispose();
throw;
}
}
public void Dispose()
{
lock (this)
{
try
{
if (this.currentConnecton != null)
{
this.currentConnecton.Close();
}
}
catch
{
}
finally
{
this.currentConnecton = null;
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
{
"Scripts":
[
"ZnVuY3Rpb24gdDMgeyAxMjsgcmV0dXJuICJ0ZXN0IiB9",
"ZnVuY3Rpb24gTmV3LVBlcnNvbigpDQp7DQogIHBhcmFtICgkRmlyc3ROYW1lLCAkTGFzdE5hbWUsICRQaG9uZSkNCg0KICAkcGVyc29uID0gbmV3LW9iamVjdCBQU09iamVjdA0KDQogICRwZXJzb24gfCBhZGQtbWVtYmVyIC10eXBlIE5vdGVQcm9wZXJ0eSAtTmFtZSBGaXJzdCAtVmFsdWUgJEZpcnN0TmFtZQ0KICAkcGVyc29uIHwgYWRkLW1lbWJlciAtdHlwZSBOb3RlUHJvcGVydHkgLU5hbWUgTGFzdCAtVmFsdWUgJExhc3ROYW1lDQogICRwZXJzb24gfCBhZGQtbWVtYmVyIC10eXBlIE5vdGVQcm9wZXJ0eSAtTmFtZSBQaG9uZSAtVmFsdWUgJFBob25lDQoNCiAgcmV0dXJuICRwZXJzb24NCn0=",
"ZnVuY3Rpb24gVGVzdFRocm93KCkNCnsNCglUaHJvdyBbc3lzdGVtLkluZGV4T3V0T2ZSYW5nZUV4Y2VwdGlvbl0gDQp9"
],
"Commands" :
[
{
"Name": "New-Person",
"Arguments" :
{
"FirstName": "MyFirstName",
"LastName": "MyLastName",
"Phone": "123-456"
}
},
{
"Name": "t3",
"Arguments" :
{
}
},
{
"Name": "Get-Date",
},
{
"Name": "TestThrow",
}
],
"RebootOnCompletion": 0
}

View File

@@ -0,0 +1,111 @@
using System;
using System.Configuration.Install;
using System.Reflection;
using System.ServiceProcess;
using NLog;
namespace Mirantis.Keero.WindowsAgent
{
public class ServiceManager
{
private readonly string serviceName;
public ServiceManager(string serviceName)
{
this.serviceName = serviceName;
}
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public bool Restart(string[] args, TimeSpan timeout)
{
var service = new ServiceController(serviceName);
try
{
var millisec1 = TimeSpan.FromMilliseconds(Environment.TickCount);
service.Stop();
service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
Log.Info("Service is stopped");
// count the rest of the timeout
var millisec2 = TimeSpan.FromMilliseconds(Environment.TickCount);
timeout = timeout - (millisec2 - millisec1);
service.Start(args);
service.WaitForStatus(ServiceControllerStatus.Running, timeout);
Log.Info("Service has started");
return true;
}
catch (Exception ex)
{
Log.ErrorException("Cannot restart service " + serviceName, ex);
return false;
}
}
public bool Stop(TimeSpan timeout)
{
var service = new ServiceController(serviceName);
try
{
service.Stop();
service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
return true;
}
catch (Exception ex)
{
Log.ErrorException("Cannot stop service " + serviceName, ex);
return false;
}
}
public bool Start(string[] args, TimeSpan timeout)
{
var service = new ServiceController(serviceName);
try
{
service.Start(args);
service.WaitForStatus(ServiceControllerStatus.Running, timeout);
return true;
}
catch (Exception ex)
{
Log.ErrorException("Cannot start service " + serviceName, ex);
return false;
}
}
public bool Install()
{
try
{
ManagedInstallerClass.InstallHelper(
new string[] { Assembly.GetEntryAssembly().Location });
}
catch(Exception ex)
{
Log.ErrorException("Cannot install service " + serviceName, ex);
return false;
}
return true;
}
public bool Uninstall()
{
try
{
ManagedInstallerClass.InstallHelper(
new string[] { "/u", Assembly.GetEntryAssembly().Location });
}
catch (Exception ex)
{
Log.ErrorException("Cannot uninstall service " + serviceName, ex);
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Mirantis.Keero.WindowsAgent</RootNamespace>
<AssemblyName>WindowsAgent</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog">
<HintPath>..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="RabbitMQ.Client">
<HintPath>..\packages\RabbitMQ.Client.3.0.2\lib\net30\RabbitMQ.Client.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll</HintPath>
</Reference>
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ExecutionPlan.cs" />
<Compile Include="MqMessage.cs" />
<Compile Include="PlanExecutor.cs" />
<Compile Include="Program.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RabbitMqClient.cs" />
<Compile Include="ServiceManager.cs" />
<Compile Include="WindowsService.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="WindowsServiceInstaller.cs">
<SubType>Component</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
<None Include="SampleExecutionPlan.json" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>$(SolutionDir)Tools\nuget install $(ProjectDir)packages.config -o $(SolutionDir)Packages</PreBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,95 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
using NLog;
namespace Mirantis.Keero.WindowsAgent
{
public abstract class WindowsService : ServiceBase
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public bool RunningAsService { get; private set; }
protected static void Start(WindowsService service, string[] arguments)
{
Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location));
if (arguments.Contains("/install", StringComparer.OrdinalIgnoreCase))
{
new ServiceManager(service.ServiceName).Install();
}
else if (arguments.Contains("/uninstall", StringComparer.OrdinalIgnoreCase))
{
new ServiceManager(service.ServiceName).Uninstall();
}
else if (arguments.Contains("/start", StringComparer.OrdinalIgnoreCase))
{
new ServiceManager(service.ServiceName).Start(Environment.GetCommandLineArgs(), TimeSpan.FromMinutes(1));
}
else if (arguments.Contains("/stop", StringComparer.OrdinalIgnoreCase))
{
new ServiceManager(service.ServiceName).Stop(TimeSpan.FromMinutes(1));
}
else if (arguments.Contains("/restart", StringComparer.OrdinalIgnoreCase))
{
new ServiceManager(service.ServiceName).Restart(Environment.GetCommandLineArgs(), TimeSpan.FromMinutes(1));
}
else if (!arguments.Contains("/console", StringComparer.OrdinalIgnoreCase))
{
service.RunningAsService = true;
Run(service);
}
else
{
try
{
service.RunningAsService = false;
Console.Title = service.ServiceName;
service.OnStart(Environment.GetCommandLineArgs());
service.WaitForExitSignal();
}
finally
{
service.OnStop();
service.Dispose();
}
}
}
protected WindowsService()
{
var displayNameAttribute =
this.GetType().GetCustomAttributes(typeof (DisplayNameAttribute), false).Cast<DisplayNameAttribute>().
FirstOrDefault();
if(displayNameAttribute != null)
{
ServiceName = displayNameAttribute.DisplayName;
}
}
protected virtual void WaitForExitSignal()
{
Console.WriteLine("Press ESC to exit");
while (Console.ReadKey(true).Key != ConsoleKey.Escape)
{
}
}
protected override void OnStart(string[] args)
{
Log.Info("Service {0} started", ServiceName);
base.OnStart(args);
}
protected override void OnStop()
{
Log.Info("Service {0} exited", ServiceName);
base.OnStop();
}
}
}

View File

@@ -0,0 +1,39 @@
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
namespace Mirantis.Keero.WindowsAgent
{
[RunInstaller(true)]
public class WindowsServiceInstaller : Installer
{
public WindowsServiceInstaller()
{
var processInstaller = new ServiceProcessInstaller { Account = ServiceAccount.LocalSystem };
foreach (var type in Assembly.GetEntryAssembly().GetExportedTypes().Where(t => t.IsSubclassOf(typeof(ServiceBase))))
{
var nameAttribute = type.GetCustomAttributes(typeof (DisplayNameAttribute), false)
.Cast<DisplayNameAttribute>().FirstOrDefault();
if(nameAttribute == null) continue;
var serviceInstaller = new ServiceInstaller {
StartType = ServiceStartMode.Automatic,
ServiceName = nameAttribute.DisplayName,
DisplayName = nameAttribute.DisplayName
};
var descriptionAttribute = type.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>().FirstOrDefault();
if(descriptionAttribute != null)
{
serviceInstaller.Description = descriptionAttribute.Description;
}
Installers.Add(serviceInstaller);
}
Installers.Add(processInstaller);
}
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" />
<package id="NLog" version="2.0.0.2000" targetFramework="net45" />
<package id="RabbitMQ.Client" version="3.0.2" targetFramework="net45" />
</packages>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<repositories>
<repository path="..\WindowsAgent\packages.config" />
</repositories>

31
dashboard/ReadMe.txt Normal file
View File

@@ -0,0 +1,31 @@
# TO DO:
# 1. Add new functional for services and data centers
# 2. Fix issue with list of services: services table shoudl show services for
# specific data center
This file is described how to install new tab on horizon dashboard.
We should do the following:
1. Copy directory 'windc' to directory '/opt/stack/horizon/openstack_dashboard/dashboards/project'
2. Copy api/windc.py to directory '/opt/stack/horizon/openstack_dashboard/api'
3. Copy directory 'windcclient' to directory '/opt/stack/horizon/'
4. Edit file '/opt/stack/horizon/openstack_dashboard/dashboards/project/dashboard.py'
Add line with windc project:
...
class BasePanels(horizon.PanelGroup):
slug = "compute"
name = _("Manage Compute")
panels = ('overview',
'instances',
'volumes',
'images_and_snapshots',
'access_and_security',
'networks',
'routers',
'windc')
...
5. Run the test Django server:
cd /opt/stack/horizon
python manage.py runserver 67.207.197.36:8080

66
dashboard/api/windc.py Normal file
View File

@@ -0,0 +1,66 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import urlparse
from django.utils.decorators import available_attrs
from windcclient.v1 import client as windc_client
__all__ = ('datacenter_get','datacenter_list',
'datacenter_create','datacenter_delete')
LOG = logging.getLogger(__name__)
def windcclient(request):
o = urlparse.urlparse("http://127.0.0.1:8082")
url = "http://127.0.0.1:8082/foo"
LOG.debug('windcclient connection created using token "%s" and url "%s"'
% (request.user.token, url))
return windc_client.Client(endpoint=url, token=None)
def datacenters_create(request, parameters):
name = parameters.get('name', '')
return windcclient(request).datacenters.create(name)
def datacenters_delete(request, datacenter_id):
return windcclient(request).datacenters.delete(datacenter_id)
def datacenters_get(request, datacenter_id):
return windcclient(request).datacenters.get(datacenter_id)
def datacenters_list(request):
return windcclient(request).datacenters.list()
def services_create(request, datacenter, parameters):
name = parameters.get('name', '')
return windcclient(request).services.create(datacenter, name)
def services_list(request, datacenter):
return windcclient(request).services.list(datacenter)
def services_get(request, datacenter, service_id):
return windcclient(request).services.get(datacenter, service_id)
def services_delete(request, datacenter, service_id):
return windcclient(request).services.delete(datacenter, service_id)

View File

52
dashboard/windc/forms.py Normal file
View File

@@ -0,0 +1,52 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from openstack_dashboard import api
from horizon import exceptions
from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__)
class UpdateWinDC(forms.SelfHandlingForm):
tenant_id = forms.CharField(widget=forms.HiddenInput)
data_center = forms.CharField(widget=forms.HiddenInput)
name = forms.CharField(required=True)
def handle(self, request, data):
try:
server = api.nova.server_update(request, data['data_center'],
data['name'])
messages.success(request,
_('Data Center "%s" updated.') % data['name'])
return server
except:
redirect = reverse("horizon:project:windc:index")
exceptions.handle(request,
_('Unable to update data center.'),
redirect=redirect)

29
dashboard/windc/panel.py Normal file
View File

@@ -0,0 +1,29 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.dashboards.project import dashboard
class WinDC(horizon.Panel):
name = _("Windows Data Centers")
slug = 'windc'
dashboard.Project.register(WinDC)

140
dashboard/windc/tables.py Normal file
View File

@@ -0,0 +1,140 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# TO DO: clear extra modules
import logging
from django import shortcuts
from django import template
from django.core import urlresolvers
from django.template.defaultfilters import title
from django.utils.http import urlencode
from django.utils.translation import string_concat, ugettext_lazy as _
from horizon.conf import HORIZON_CONFIG
from horizon import exceptions
from horizon import messages
from horizon import tables
from horizon.templatetags import sizeformat
from horizon.utils.filters import replace_underscores
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.access_and_security \
.floating_ips.workflows import IPAssociationWorkflow
LOG = logging.getLogger(__name__)
class CreateService(tables.LinkAction):
name = "CreateService"
verbose_name = _("Create Service")
url = "horizon:project:windc:create"
classes = ("btn-launch", "ajax-modal")
def allowed(self, request, datum):
return True
def action(self, request, service):
# FIX ME
api.windc.services_create(request, service)
class CreateDataCenter(tables.LinkAction):
name = "CreateDataCenter"
verbose_name = _("Create Windows Data Center")
url = "horizon:project:windc:create_dc"
classes = ("btn-launch", "ajax-modal")
def allowed(self, request, datum):
return True
def action(self, request, datacenter):
api.windc.datacenters_create(request, datacenter)
class DeleteDataCenter(tables.BatchAction):
name = "delete"
action_present = _("Delete")
action_past = _("Delete")
data_type_singular = _("Data Center")
data_type_plural = _("Data Center")
classes = ('btn-danger', 'btn-terminate')
def allowed(self, request, datum):
return True
def action(self, request, datacenter_id):
datacenter = api.windc.datacenters_get(request, datacenter_id)
api.windc.datacenters_delete(request, datacenter)
class EditService(tables.LinkAction):
name = "edit"
verbose_name = _("Edit Service")
url = "horizon:project:windc:update"
classes = ("ajax-modal", "btn-edit")
def allowed(self, request, instance):
return True
class ShowDataCenterServices(tables.LinkAction):
name = "edit"
verbose_name = _("Services")
url = "horizon:project:windc:services"
def allowed(self, request, instance):
return True
class UpdateRow(tables.Row):
ajax = True
def get_data(self, request, instance_id):
instance = api.nova.server_get(request, instance_id)
instance.full_flavor = api.nova.flavor_get(request,
instance.flavor["id"])
return instance
class WinDCTable(tables.DataTable):
name = tables.Column("name",
link=("horizon:project:windc:services"),
verbose_name=_("Name"))
class Meta:
name = "windc"
verbose_name = _("Windows Data Centers")
row_class = UpdateRow
table_actions = (CreateDataCenter,)
row_actions = (ShowDataCenterServices,DeleteDataCenter)
class WinServicesTable(tables.DataTable):
name = tables.Column("name",
link=("horizon:project:windc"),
verbose_name=_("Name"))
class Meta:
name = "services"
verbose_name = _("Services")
row_class = UpdateRow
table_actions = (CreateService,)
row_actions = (EditService,)

38
dashboard/windc/tabs.py Normal file
View File

@@ -0,0 +1,38 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
class OverviewTab(tabs.Tab):
name = _("Services")
slug = "_services"
template_name = ("project/windc/_services.html")
def get_context_data(self, request):
dc = self.tab_group.kwargs['domain_controller']
return {"domain_controller": dc}
class WinServicesTab(tabs.TabGroup):
slug = "services_details"
tabs = (OverviewTab,)
sticky = True

View File

@@ -0,0 +1,2 @@
{% load i18n %}
<p>{% blocktrans %}Data Center is an instance with different services.{% endblocktrans %}</p>

View File

@@ -0,0 +1,2 @@
{% load i18n %}
<p>{% blocktrans %}You can deploy few domain controllers with one name.{% endblocktrans %}</p>

View File

@@ -0,0 +1,2 @@
{% load i18n %}
<p>{% blocktrans %}You can deploy few Internet Information Services in one domain.{% endblocktrans %}</p>

View File

@@ -0,0 +1,3 @@
{% load i18n sizeformat %}
<h3>{% trans "Services" %}</h3>

View File

@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Windows Service" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create Windows Service") %}
{% endblock page_header %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Windows Data Center" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create Windows Data Center") %}
{% endblock page_header %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Windows Data Centers" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Windows Data Centers") %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Data Center Services" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Data Center "|add:dc_name %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n sizeformat %}
{% block title %}{% trans "Services" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title="Domain Controller Services" %}
{% endblock page_header %}
{% block main %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Instance" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Update Instance") %}
{% endblock page_header %}
{% block main %}
{% include 'project/instances/_update.html' %}
{% endblock %}

34
dashboard/windc/urls.py Normal file
View File

@@ -0,0 +1,34 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls.defaults import patterns, url
from .views import IndexView, CreateWinDCView, WinServices, CreateWinServiceView
VIEW_MOD = 'openstack_dashboard.dashboards.project.windc.views'
urlpatterns = patterns(VIEW_MOD,
url(r'^$', IndexView.as_view(), name='index'),
url(r'^create$', CreateWinServiceView.as_view(), name='create'),
url(r'^create_dc$', CreateWinDCView.as_view(), name='create_dc'),
url(r'^(?P<domain_controller_id>[^/]+)/$', WinServices.as_view(),
name='services')
)

102
dashboard/windc/views.py Normal file
View File

@@ -0,0 +1,102 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations# under the License.
"""
Views for managing instances.
"""
import logging
from django import http
from django import shortcuts
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.datastructures import SortedDict
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import tabs
from horizon import tables
from horizon import workflows
from openstack_dashboard import api
from .tables import WinDCTable, WinServicesTable
from .workflows import CreateWinService, CreateWinDC
LOG = logging.getLogger(__name__)
class IndexView(tables.DataTableView):
table_class = WinDCTable
template_name = 'project/windc/index.html'
def get_data(self):
# Gather our datacenters
try:
data_centers = api.windc.datacenters_list(self.request)
except:
data_centers = []
exceptions.handle(self.request,
_('Unable to retrieve data centers list.'))
return data_centers
class WinServices(tables.DataTableView):
table_class = WinServicesTable
template_name = 'project/windc/services.html'
def get_context_data(self, **kwargs):
context = super(WinServices, self).get_context_data(**kwargs)
data = self.get_data()
context["dc_name"] = self.dc_name
return context
def get_data(self):
try:
dc_id = self.kwargs['domain_controller_id']
datacenter = api.windc.datacenters_get(self.request, dc_id)
self.dc_name = datacenter.name
services = api.windc.services_list(self.request, datacenter)
except:
services = []
exceptions.handle(self.request,
_('Unable to retrieve list of services for '
'data center "%s".') % dc_id)
return services
class CreateWinDCView(workflows.WorkflowView):
workflow_class = CreateWinDC
template_name = "project/windc/create_dc.html"
def get_initial(self):
initial = super(CreateWinDCView, self).get_initial()
initial['project_id'] = self.request.user.tenant_id
initial['user_id'] = self.request.user.id
return initial
class CreateWinServiceView(workflows.WorkflowView):
workflow_class = CreateWinService
template_name = "project/windc/create.html"
def get_initial(self):
initial = super(CreateWinServiceView, self).get_initial()
initial['project_id'] = self.request.user.tenant_id
initial['user_id'] = self.request.user.id
return initial

View File

@@ -0,0 +1,188 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import logging
from django.utils.text import normalize_newlines
from django.utils.translation import ugettext as _
from horizon import exceptions
from horizon import forms
from horizon import workflows
from openstack_dashboard import api
LOG = logging.getLogger(__name__)
class SelectProjectUserAction(workflows.Action):
project_id = forms.ChoiceField(label=_("Project"))
user_id = forms.ChoiceField(label=_("User"))
def __init__(self, request, *args, **kwargs):
super(SelectProjectUserAction, self).__init__(request, *args, **kwargs)
# Set our project choices
projects = [(tenant.id, tenant.name)
for tenant in request.user.authorized_tenants]
self.fields['project_id'].choices = projects
# Set our user options
users = [(request.user.id, request.user.username)]
self.fields['user_id'].choices = users
class Meta:
name = _("Project & User")
# Unusable permission so this is always hidden. However, we
# keep this step in the workflow for validation/verification purposes.
permissions = ("!",)
class SelectProjectUser(workflows.Step):
action_class = SelectProjectUserAction
class ConfigureDCAction(workflows.Action):
name = forms.CharField(label=_("Data Center Name"),
required=True)
class Meta:
name = _("Data Center")
help_text_template = ("project/windc/_data_center_help.html")
class ConfigureDC(workflows.Step):
action_class = ConfigureDCAction
contibutes = ("name",)
def contribute(self, data, context):
if data:
context['name'] = data.get("name", "")
return context
class ConfigureWinDCAction(workflows.Action):
dc_name = forms.CharField(label=_("Domain Name"),
required=False)
dc_net_name = forms.CharField(label=_("Domain NetBIOS Name"),
required=False,
help_text=_("A NetBIOS name of new domain."))
dc_count = forms.IntegerField(label=_("Domain Controllers Count"),
required=True,
min_value=1,
max_value=100,
initial=1)
adm_password = forms.CharField(widget=forms.PasswordInput,
label=_("Administrator password"),
required=False,
help_text=_("Password for "
"administrator account."))
recovery_password = forms.CharField(widget=forms.PasswordInput,
label=_("Recovery password"),
required=False,
help_text=_("Password for "
"Active Directory "
"Recovery Mode."))
class Meta:
name = _("Domain Controllers")
help_text_template = ("project/windc/_dc_help.html")
class ConfigureWinDC(workflows.Step):
action_class = ConfigureWinDCAction
class ConfigureWinIISAction(workflows.Action):
iis_name = forms.CharField(label=_("IIS Server Name"),
required=False)
iis_count = forms.IntegerField(label=_("IIS Servers Count"),
required=True,
min_value=1,
max_value=100,
initial=1)
iis_domain = forms.CharField(label=_("Member of the Domain"),
required=False,
help_text=_("A name of domain for"
" IIS Server."))
class Meta:
name = _("Internet Information Services")
help_text_template = ("project/windc/_iis_help.html")
class ConfigureWinIIS(workflows.Step):
action_class = ConfigureWinIISAction
class CreateWinService(workflows.Workflow):
slug = "create"
name = _("Create Service")
finalize_button_name = _("Deploy")
success_message = _('Created service "%s".')
failure_message = _('Unable to create service "%s".')
success_url = "horizon:project:windc:services"
default_steps = (SelectProjectUser,
ConfigureWinDC,
ConfigureWinIIS)
def format_status_message(self, message):
name = self.context.get('name', 'noname')
return message % name
def handle(self, request, context):
try:
datacenter = context.get('domain_controller_name', '')
service = api.windc.services_create(request, context)
return True
except:
exceptions.handle(request)
return False
class CreateWinDC(workflows.Workflow):
slug = "create"
name = _("Create Windows Data Center")
finalize_button_name = _("Create")
success_message = _('Created data center "%s".')
failure_message = _('Unable to create data center "%s".')
success_url = "horizon:project:windc:index"
default_steps = (SelectProjectUser,
ConfigureDC)
def format_status_message(self, message):
name = self.context.get('name', 'noname')
return message % name
def handle(self, request, context):
try:
datacenter = api.windc.datacenters_create(request, context)
return True
except:
exceptions.handle(request)
return False

View File

View File

View File

@@ -0,0 +1,137 @@
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Base utilities to build API operation managers and objects on top of.
"""
def getid(obj):
"""
Abstracts the common pattern of allowing both an object or an object's ID
(UUID) as a parameter when dealing with relationships.
"""
try:
return obj.id
except AttributeError:
return obj
class Manager(object):
"""
Managers interact with a particular type of API and provide CRUD
operations for them.
"""
resource_class = None
def __init__(self, api):
self.api = api
def _list(self, url, response_key, obj_class=None, body=None):
resp, body = self.api.client.json_request('GET', url, body=body)
if obj_class is None:
obj_class = self.resource_class
data = body[response_key]
return [obj_class(self, res, loaded=True) for res in data if res]
def _delete(self, url):
self.api.client.raw_request('DELETE', url)
def _update(self, url, body, response_key=None):
resp, body = self.api.client.json_request('PUT', url, body=body)
# PUT requests may not return a body
if body:
return self.resource_class(self, body[response_key])
def _create(self, url, body, response_key, return_raw=False):
resp, body = self.api.client.json_request('POST', url, body=body)
if return_raw:
return body[response_key]
return self.resource_class(self, body[response_key])
def _get(self, url, response_key, return_raw=False):
resp, body = self.api.client.json_request('GET', url)
if return_raw:
return body[response_key]
return self.resource_class(self, body[response_key])
class Resource(object):
"""
A resource represents a particular instance of an object (tenant, user,
etc). This is pretty much just a bag for attributes.
:param manager: Manager object
:param info: dictionary representing resource attributes
:param loaded: prevent lazy-loading if set to True
"""
def __init__(self, manager, info, loaded=False):
self.manager = manager
self._info = info
self._add_details(info)
self._loaded = loaded
def _add_details(self, info):
for (k, v) in info.iteritems():
setattr(self, k, v)
def __getattr__(self, k):
if k not in self.__dict__:
#NOTE(bcwaldon): disallow lazy-loading if already loaded once
if not self.is_loaded():
self.get()
return self.__getattr__(k)
raise AttributeError(k)
else:
return self.__dict__[k]
def __repr__(self):
reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
k != 'manager')
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info)
def get_info(self):
if not self.is_loaded():
self.get()
if self._info:
return self._info.copy()
return {}
def get(self):
# set_loaded() first ... so if we have to bail, we know we tried.
self.set_loaded(True)
if not hasattr(self.manager, 'get'):
return
new = self.manager.get(self.id)
if new:
self._info = new._info
self._add_details(new._info)
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
if hasattr(self, 'id') and hasattr(other, 'id'):
return self.id == other.id
return self._info == other._info
def is_loaded(self):
return self._loaded
def set_loaded(self, val):
self._loaded = val

View File

@@ -0,0 +1,151 @@
# Copyright 2012 OpenStack LLC.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
"""
OpenStack Client interface. Handles the REST calls and responses.
"""
import httplib2
import copy
import logging
import json
from . import exceptions
from . import utils
from .service_catalog import ServiceCatalog
logger = logging.getLogger(__name__)
class HTTPClient(httplib2.Http):
USER_AGENT = 'python-balancerclient'
def __init__(self, endpoint=None, token=None, username=None,
password=None, tenant_name=None, tenant_id=None,
region_name=None, auth_url=None, auth_tenant_id=None,
timeout=600, insecure=False):
super(HTTPClient, self).__init__(timeout=timeout)
self.endpoint = endpoint
self.auth_token = token
self.auth_url = auth_url
self.auth_tenant_id = auth_tenant_id
self.username = username
self.password = password
self.tenant_name = tenant_name
self.tenant_id = tenant_id
self.region_name = region_name
self.force_exception_to_status_code = True
self.disable_ssl_certificate_validation = insecure
if self.endpoint is None:
self.authenticate()
def _http_request(self, url, method, **kwargs):
""" Send an http request with the specified characteristics.
"""
kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
kwargs['headers'].setdefault('User-Agent', self.USER_AGENT)
if self.auth_token:
kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
resp, body = super(HTTPClient, self).request(url, method, **kwargs)
if logger.isEnabledFor(logging.DEBUG):
utils.http_log(logger, (url, method,), kwargs, resp, body)
if resp.status in (301, 302, 305):
return self._http_request(resp['location'], method, **kwargs)
return resp, body
def _json_request(self, method, url, **kwargs):
""" Wrapper around _http_request to handle setting headers,
JSON enconding/decoding and error handling.
"""
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type', 'application/json')
if 'body' in kwargs and kwargs['body'] is not None:
kwargs['body'] = json.dumps(kwargs['body'])
resp, body = self._http_request(url, method, **kwargs)
if body:
try:
body = json.loads(body)
except ValueError:
logger.debug("Could not decode JSON from body: %s" % body)
else:
logger.debug("No body was returned.")
body = None
if 400 <= resp.status < 600:
# DELETE THIS STRING
logger.exception(url)
raise exceptions.from_response(resp, body)
return resp, body
def raw_request(self, method, url, **kwargs):
url = self.endpoint + url
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type',
'application/octet-stream')
resp, body = self._http_request(url, method, **kwargs)
if 400 <= resp.status < 600:
logger.exception(url)
raise exceptions.from_response(resp, body)
return resp, body
def json_request(self, method, url, **kwargs):
url = self.endpoint + url
resp, body = self._json_request(method, url, **kwargs)
return resp, body
def authenticate(self):
token_url = self.auth_url + "/tokens"
body = {'auth': {'passwordCredentials': {'username': self.username,
'password': self.password}}}
if self.tenant_id:
body['auth']['tenantId'] = self.tenant_id
elif self.tenant_name:
body['auth']['tenantName'] = self.tenant_name
tmp_follow_all_redirects = self.follow_all_redirects
self.follow_all_redirects = True
try:
resp, body = self._json_request('POST', token_url, body=body)
finally:
self.follow_all_redirects = tmp_follow_all_redirects
try:
self.service_catalog = ServiceCatalog(body['access'])
token = self.service_catalog.get_token()
self.auth_token = token['id']
self.auth_tenant_id = token['tenant_id']
except KeyError:
logger.exception("Parse service catalog failed.")
raise exceptions.AuthorizationFailure()
self.endpoint = self.service_catalog.url_for(attr='region',
filter_value=self.region_name)

View File

@@ -0,0 +1,140 @@
# Copyright 2010 Jacob Kaplan-Moss
"""
Exception definitions.
"""
class UnsupportedVersion(Exception):
"""Indicates that the user is trying to use an unsupported
version of the API"""
pass
class CommandError(Exception):
pass
class AuthorizationFailure(Exception):
pass
class NoUniqueMatch(Exception):
pass
class NoTokenLookupException(Exception):
"""This form of authentication does not support looking up
endpoints from an existing token."""
pass
class EndpointNotFound(Exception):
"""Could not find Service or Region in Service Catalog."""
pass
class AmbiguousEndpoints(Exception):
"""Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None):
self.endpoints = endpoints
def __str__(self):
return "AmbiguousEndpoints: %s" % repr(self.endpoints)
class ClientException(Exception):
"""
The base exception class for all exceptions this library raises.
"""
def __init__(self, code, message=None, details=None):
self.code = code
self.message = message or self.__class__.message
self.details = details
def __str__(self):
return "%s (HTTP %s)" % (self.message, self.code)
class BadRequest(ClientException):
"""
HTTP 400 - Bad request: you sent some malformed data.
"""
http_status = 400
message = "Bad request"
class Unauthorized(ClientException):
"""
HTTP 401 - Unauthorized: bad credentials.
"""
http_status = 401
message = "Unauthorized"
class Forbidden(ClientException):
"""
HTTP 403 - Forbidden: your credentials don't give you access to this
resource.
"""
http_status = 403
message = "Forbidden"
class NotFound(ClientException):
"""
HTTP 404 - Not found
"""
http_status = 404
message = "Not found"
class OverLimit(ClientException):
"""
HTTP 413 - Over limit: you're over the API limits for this time period.
"""
http_status = 413
message = "Over limit"
# NotImplemented is a python keyword.
class HTTPNotImplemented(ClientException):
"""
HTTP 501 - Not Implemented: the server does not support this operation.
"""
http_status = 501
message = "Not Implemented"
# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__()
# so we can do this:
# _code_map = dict((c.http_status, c)
# for c in ClientException.__subclasses__())
#
# Instead, we have to hardcode it:
_code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized,
Forbidden, NotFound, OverLimit, HTTPNotImplemented])
def from_response(response, body):
"""
Return an instance of an ClientException or subclass
based on an httplib2 response.
Usage::
resp, body = http.request(...)
if resp.status != 200:
raise exception_from_response(resp, body)
"""
cls = _code_map.get(response.status, ClientException)
if body:
if hasattr(body, 'keys'):
error = body[body.keys()[0]]
message = error.get('message', None)
details = error.get('details', None)
else:
message = 'n/a'
details = body
return cls(code=response.status, message=message, details=details)
else:
return cls(code=response.status)

View File

@@ -0,0 +1,62 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011, Piston Cloud Computing, Inc.
# Copyright 2011 Nebula, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import exceptions
class ServiceCatalog(object):
"""Helper methods for dealing with a Keystone Service Catalog."""
def __init__(self, resource_dict):
self.catalog = resource_dict
def get_token(self):
"""Fetch token details fron service catalog"""
token = {'id': self.catalog['token']['id'],
'expires': self.catalog['token']['expires']}
try:
token['user_id'] = self.catalog['user']['id']
token['tenant_id'] = self.catalog['token']['tenant']['id']
except:
# just leave the tenant and user out if it doesn't exist
pass
return token
def url_for(self, attr=None, filter_value=None,
service_type='loadbalancer', endpoint_type='publicURL'):
"""Fetch an endpoint from the service catalog.
Fetch the specified endpoint from the service catalog for
a particular endpoint attribute. If no attribute is given, return
the first endpoint of the specified type.
See tests for a sample service catalog.
"""
catalog = self.catalog.get('serviceCatalog', [])
for service in catalog:
if service['type'] != service_type:
continue
endpoints = service['endpoints']
for endpoint in endpoints:
if not filter_value or endpoint.get(attr) == filter_value:
return endpoint[endpoint_type]
raise exceptions.EndpointNotFound('Endpoint not found.')

View File

@@ -0,0 +1,291 @@
import os
import re
import sys
import uuid
import logging
import prettytable
from . import exceptions
def arg(*args, **kwargs):
"""Decorator for CLI args."""
def _decorator(func):
add_arg(func, *args, **kwargs)
return func
return _decorator
def env(*vars, **kwargs):
"""
returns the first environment variable set
if none are non-empty, defaults to '' or keyword arg default
"""
for v in vars:
value = os.environ.get(v, None)
if value:
return value
return kwargs.get('default', '')
def add_arg(f, *args, **kwargs):
"""Bind CLI arguments to a shell.py `do_foo` function."""
if not hasattr(f, 'arguments'):
f.arguments = []
# NOTE(sirp): avoid dups that can occur when the module is shared across
# tests.
if (args, kwargs) not in f.arguments:
# Because of the sematics of decorator composition if we just append
# to the options list positional options will appear to be backwards.
f.arguments.insert(0, (args, kwargs))
def add_resource_manager_extra_kwargs_hook(f, hook):
"""Adds hook to bind CLI arguments to ResourceManager calls.
The `do_foo` calls in shell.py will receive CLI args and then in turn pass
them through to the ResourceManager. Before passing through the args, the
hooks registered here will be called, giving us a chance to add extra
kwargs (taken from the command-line) to what's passed to the
ResourceManager.
"""
if not hasattr(f, 'resource_manager_kwargs_hooks'):
f.resource_manager_kwargs_hooks = []
names = [h.__name__ for h in f.resource_manager_kwargs_hooks]
if hook.__name__ not in names:
f.resource_manager_kwargs_hooks.append(hook)
def get_resource_manager_extra_kwargs(f, args, allow_conflicts=False):
"""Return extra_kwargs by calling resource manager kwargs hooks."""
hooks = getattr(f, "resource_manager_kwargs_hooks", [])
extra_kwargs = {}
for hook in hooks:
hook_name = hook.__name__
hook_kwargs = hook(args)
conflicting_keys = set(hook_kwargs.keys()) & set(extra_kwargs.keys())
if conflicting_keys and not allow_conflicts:
raise Exception("Hook '%(hook_name)s' is attempting to redefine"
" attributes '%(conflicting_keys)s'" % locals())
extra_kwargs.update(hook_kwargs)
return extra_kwargs
def unauthenticated(f):
"""
Adds 'unauthenticated' attribute to decorated function.
Usage:
@unauthenticated
def mymethod(f):
...
"""
f.unauthenticated = True
return f
def isunauthenticated(f):
"""
Checks to see if the function is marked as not requiring authentication
with the @unauthenticated decorator. Returns True if decorator is
set to True, False otherwise.
"""
return getattr(f, 'unauthenticated', False)
def service_type(stype):
"""
Adds 'service_type' attribute to decorated function.
Usage:
@service_type('volume')
def mymethod(f):
...
"""
def inner(f):
f.service_type = stype
return f
return inner
def get_service_type(f):
"""
Retrieves service type from function
"""
return getattr(f, 'service_type', None)
def pretty_choice_list(l):
return ', '.join("'%s'" % i for i in l)
def print_list(objs, fields, formatters={}, sortby_index=0):
if sortby_index == None:
sortby = None
else:
sortby = fields[sortby_index]
pt = prettytable.PrettyTable([f for f in fields], caching=False)
pt.align = 'l'
for o in objs:
row = []
for field in fields:
if field in formatters:
row.append(formatters[field](o))
else:
field_name = field.lower().replace(' ', '_')
data = getattr(o, field_name, '')
row.append(data)
pt.add_row(row)
print pt.get_string(sortby=sortby)
def print_flat_list(lst, field):
pt = prettytable.PrettyTable(field)
for el in lst:
pt.add_row([el])
print pt.get_string()
def print_dict(d, property="Property"):
pt = prettytable.PrettyTable([property, 'Value'], caching=False)
pt.align = 'l'
[pt.add_row(list(r)) for r in d.iteritems()]
print pt.get_string(sortby=property)
def find_resource(manager, name_or_id):
"""Helper for the _find_* methods."""
# first try to get entity as integer id
try:
if isinstance(name_or_id, int) or name_or_id.isdigit():
return manager.get(int(name_or_id))
except exceptions.NotFound:
pass
# now try to get entity as uuid
try:
uuid.UUID(str(name_or_id))
return manager.get(name_or_id)
except (ValueError, exceptions.NotFound):
pass
try:
try:
return manager.find(human_id=name_or_id)
except exceptions.NotFound:
pass
# finally try to find entity by name
try:
return manager.find(name=name_or_id)
except exceptions.NotFound:
try:
# Volumes does not have name, but display_name
return manager.find(display_name=name_or_id)
except exceptions.NotFound:
msg = "No %s with a name or ID of '%s' exists." % \
(manager.resource_class.__name__.lower(), name_or_id)
raise exceptions.CommandError(msg)
except exceptions.NoUniqueMatch:
msg = ("Multiple %s matches found for '%s', use an ID to be more"
" specific." % (manager.resource_class.__name__.lower(),
name_or_id))
raise exceptions.CommandError(msg)
def _format_servers_list_networks(server):
output = []
for (network, addresses) in server.networks.items():
if len(addresses) == 0:
continue
addresses_csv = ', '.join(addresses)
group = "%s=%s" % (network, addresses_csv)
output.append(group)
return '; '.join(output)
class HookableMixin(object):
"""Mixin so classes can register and run hooks."""
_hooks_map = {}
@classmethod
def add_hook(cls, hook_type, hook_func):
if hook_type not in cls._hooks_map:
cls._hooks_map[hook_type] = []
cls._hooks_map[hook_type].append(hook_func)
@classmethod
def run_hooks(cls, hook_type, *args, **kwargs):
hook_funcs = cls._hooks_map.get(hook_type) or []
for hook_func in hook_funcs:
hook_func(*args, **kwargs)
def safe_issubclass(*args):
"""Like issubclass, but will just return False if not a class."""
try:
if issubclass(*args):
return True
except TypeError:
pass
return False
def import_class(import_str):
"""Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')
__import__(mod_str)
return getattr(sys.modules[mod_str], class_str)
_slugify_strip_re = re.compile(r'[^\w\s-]')
_slugify_hyphenate_re = re.compile(r'[-\s]+')
# http://code.activestate.com/recipes/
# 577257-slugify-make-a-string-usable-in-a-url-or-filename/
def slugify(value):
"""
Normalizes string, converts to lowercase, removes non-alpha characters,
and converts spaces to hyphens.
From Django's "django/template/defaultfilters.py".
"""
import unicodedata
if not isinstance(value, unicode):
value = unicode(value)
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
value = unicode(_slugify_strip_re.sub('', value).strip().lower())
return _slugify_hyphenate_re.sub('-', value)
def http_log(logger, args, kwargs, resp, body):
# if not logger.isEnabledFor(logging.DEBUG):
# return
string_parts = ['curl -i']
for element in args:
if element in ('GET', 'POST'):
string_parts.append(' -X %s' % element)
else:
string_parts.append(' %s' % element)
for element in kwargs['headers']:
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
string_parts.append(header)
logger.debug("REQ: %s\n" % "".join(string_parts))
if 'body' in kwargs and kwargs['body']:
logger.debug("REQ BODY: %s\n" % (kwargs['body']))
logger.debug("RESP:%s\n", resp)
logger.debug("RESP BODY:%s\n", body)

View File

@@ -0,0 +1,285 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Command-line interface to the OpenStack LBaaS API.
"""
import argparse
import httplib2
import os
import sys
import logging
from balancerclient.common import exceptions as exc
from balancerclient.common import utils
from balancerclient.v1 import shell as shell_v1
LOG = logging.getLogger(__name__)
class OpenStackBalancerShell(object):
def get_base_parser(self):
parser = argparse.ArgumentParser(
prog='balancer',
description=__doc__.strip(),
epilog='See "balancer help COMMAND" '
'for help on a specific command.',
add_help=False,
formatter_class=OpenStackHelpFormatter,
)
# Global arguments
parser.add_argument('-h',
'--help',
action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('--debug',
default=False,
action='store_true',
help=argparse.SUPPRESS)
parser.add_argument('--os_username',
metavar='<auth-user-name>',
default=utils.env('OS_USERNAME'),
help='Defaults to env[OS_USERNAME]')
parser.add_argument('--os_password',
metavar='<auth-password>',
default=utils.env('OS_PASSWORD'),
help='Defaults to env[OS_PASSWORD]')
parser.add_argument('--os_tenant_name',
metavar='<auth-tenant-name>',
default=utils.env('OS_TENANT_NAME'),
help='Defaults to env[OS_TENANT_NAME]')
parser.add_argument('--os_tenant_id',
metavar='<tenant-id>',
default=utils.env('OS_TENANT_ID'),
help='Defaults to env[OS_TENANT_ID]')
parser.add_argument('--os_auth_url',
metavar='<auth-url>',
default=utils.env('OS_AUTH_URL'),
help='Defaults to env[OS_AUTH_URL]')
parser.add_argument('--os_region_name',
metavar='<region-name>',
default=utils.env('OS_REGION_NAME'),
help='Defaults to env[OS_REGION_NAME]')
parser.add_argument('--os_balancer_api_version',
metavar='<balancer-api-version>',
default=utils.env('OS_BALANCER_API_VERSION',
'KEYSTONE_VERSION'),
help='Defaults to env[OS_BALANCER_API_VERSION]'
' or 2.0')
parser.add_argument('--token',
metavar='<service-token>',
default=utils.env('SERVICE_TOKEN'),
help='Defaults to env[SERVICE_TOKEN]')
parser.add_argument('--endpoint',
metavar='<service-endpoint>',
default=utils.env('SERVICE_ENDPOINT'),
help='Defaults to env[SERVICE_ENDPOINT]')
return parser
def get_subcommand_parser(self, version):
parser = self.get_base_parser()
self.subcommands = {}
subparsers = parser.add_subparsers(metavar='<subcommand>')
try:
actions_module = {
'1': shell_v1,
}[version]
except KeyError:
actions_module = shell_v1
self._find_actions(subparsers, actions_module)
self._find_actions(subparsers, self)
return parser
def _find_actions(self, subparsers, actions_module):
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
# I prefer to be hypen-separated instead of underscores.
command = attr[3:].replace('_', '-')
callback = getattr(actions_module, attr)
desc = callback.__doc__ or ''
help = desc.strip().split('\n')[0]
arguments = getattr(callback, 'arguments', [])
subparser = subparsers.add_parser(
command,
help=help,
description=desc,
add_help=False,
formatter_class=OpenStackHelpFormatter)
subparser.add_argument('-h', '--help', action='help',
help=argparse.SUPPRESS)
self.subcommands[command] = subparser
for (args, kwargs) in arguments:
subparser.add_argument(*args, **kwargs)
subparser.set_defaults(func=callback)
def main(self, argv):
# Parse args once to find version
parser = self.get_base_parser()
(options, args) = parser.parse_known_args(argv)
# build available subcommands based on version
api_version = options.os_balancer_api_version
subcommand_parser = self.get_subcommand_parser(api_version)
self.parser = subcommand_parser
# Handle top-level --help/-h before attempting to parse
# a command off the command line
if not argv or options.help:
self.do_help(options)
return 0
# Parse args again and call whatever callback was selected
args = subcommand_parser.parse_args(argv)
# Deal with global arguments
if args.debug:
httplib2.debuglevel = 1
# Short-circuit and deal with help command right away.
if args.func == self.do_help:
self.do_help(args)
return 0
#FIXME(usrleon): Here should be restrict for project id same as
# for username or apikey but for compatibility it is not.
if not utils.isunauthenticated(args.func):
# if the user hasn't provided any auth data
if not (args.token or args.endpoint or args.os_username or
args.os_password or args.os_auth_url):
raise exc.CommandError('Expecting authentication method via \n'
' either a service token, '
'--token or env[SERVICE_TOKEN], \n'
' or credentials, '
'--os_username or env[OS_USERNAME].')
# if it looks like the user wants to provide a service token
# but is missing something
if args.token or args.endpoint and not (
args.token and args.endpoint):
if not args.token:
raise exc.CommandError(
'Expecting a token provided via either --token or '
'env[SERVICE_TOKEN]')
if not args.endpoint:
raise exc.CommandError(
'Expecting an endpoint provided via either --endpoint '
'or env[SERVICE_ENDPOINT]')
# if it looks like the user wants to provide a credentials
# but is missing something
if ((args.os_username or args.os_password or args.os_auth_url)
and not (args.os_username and args.os_password and
args.os_auth_url)):
if not args.os_username:
raise exc.CommandError(
'Expecting a username provided via either '
'--os_username or env[OS_USERNAME]')
if not args.os_password:
raise exc.CommandError(
'Expecting a password provided via either '
'--os_password or env[OS_PASSWORD]')
if not args.os_auth_url:
raise exc.CommandError(
'Expecting an auth URL via either --os_auth_url or '
'env[OS_AUTH_URL]')
if utils.isunauthenticated(args.func):
self.cs = shell_generic.CLIENT_CLASS(endpoint=args.os_auth_url)
else:
token = None
endpoint = None
if args.token and args.endpoint:
token = args.token
endpoint = args.endpoint
api_version = options.os_balancer_api_version
self.cs = self.get_api_class(api_version)(
username=args.os_username,
tenant_name=args.os_tenant_name,
tenant_id=args.os_tenant_id,
token=token,
endpoint=endpoint,
password=args.os_password,
auth_url=args.os_auth_url,
region_name=args.os_region_name)
try:
args.func(self.cs, args)
except exc.Unauthorized:
raise exc.CommandError("Invalid OpenStack LBaaS credentials.")
except exc.AuthorizationFailure:
raise exc.CommandError("Unable to authorize user")
def get_api_class(self, version):
try:
return {
"1": shell_v1.CLIENT_CLASS,
}[version]
except KeyError:
return shell_v1.CLIENT_CLASS
@utils.arg('command', metavar='<subcommand>', nargs='?',
help='Display help for <subcommand>')
def do_help(self, args):
"""
Display help about this program or one of its subcommands.
"""
if getattr(args, 'command', None):
if args.command in self.subcommands:
self.subcommands[args.command].print_help()
else:
raise exc.CommandError("'%s' is not a valid subcommand" %
args.command)
else:
self.parser.print_help()
# I'm picky about my shell help.
class OpenStackHelpFormatter(argparse.HelpFormatter):
def start_section(self, heading):
# Title-case the headings
heading = '%s%s' % (heading[0].upper(), heading[1:])
super(OpenStackHelpFormatter, self).start_section(heading)
def main():
try:
return OpenStackBalancerShell().main(sys.argv[1:])
except Exception, err:
LOG.exception("The operation executed with an error %r." % err)
raise

View File

View File

@@ -0,0 +1,29 @@
# Copyright 2012 OpenStack LLC.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from windcclient.common import client
from . import datacenters
from . import services
class Client(object):
"""Client for the WinDC v1 API."""
def __init__(self, **kwargs):
self.client = client.HTTPClient(**kwargs)
self.datacenters = datacenters.DCManager(self)
self.services = services.DCServiceManager(self)

View File

@@ -0,0 +1,44 @@
# Copyright 2012 OpenStack LLC.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from windcclient.common import base
class DC(base.Resource):
"""Represent load balancer device instance."""
def __repr__(self):
return "<DC(%s)>" % self._info
class DCManager(base.Manager):
resource_class = DC
def list(self):
return self._list('/datacenters', 'datacenters')
def create(self, name, **extra):
body = {'name': name, 'services': {}}
body.update(extra)
return self._create('/datacenters', body, 'datacenter')
def delete(self, datacenter):
return self._delete("/datacenters/%s" % base.getid(datacenter))
def get(self, datacenter):
return self._get("/datacenters/%s" % base.getid(datacenter),
'datacenter')

View File

@@ -0,0 +1,48 @@
# Copyright 2012 OpenStack LLC.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
from windcclient.common import base
class DCService(base.Resource):
def __repr__(self):
return "<Service(%s)>" % self._info
class DCServiceManager(base.Manager):
resource_class = DCService
def list(self, datacenter):
return self._list('/datacenters/%s' % base.getid(datacenter),
'services')
def create(self, datacenter, name, **extra):
body = {'name': name,}
body.update(extra)
return self._create('/datacenters/%s' % base.getid(datacenter),
body, 'service')
def delete(self, datacenter, service):
return self._delete("/datacenters/%s/%s" % \
(base.getid(datacenter),
base.getid(service)))
def get(self, datacenter, service):
return self._get("/datacenters/%s/%s" % \
(base.getid(datacenter),
base.getid(service)),
'service')

5
windc/heat_run Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
source openrc.sh
heat "$@"

View File

@@ -4,5 +4,5 @@
"domain": "ACME.cloud",
"AdminUser": "Admin",
"AdminPassword": "StrongPassword",
"DomainControllerNames": ["APP-AD001","APP-AD002"]
"DomainControllerNames": ["AD-DC001"]
}

View File

@@ -3,7 +3,7 @@
# package to get the right headers...
greenlet>=0.3.1
SQLAlchemy>=0.7
SQLAlchemy<=0.7.9
anyjson
eventlet>=0.9.12
PasteDeploy
@@ -15,7 +15,7 @@ sqlalchemy-migrate>=0.7.2
httplib2
kombu
iso8601>=0.1.4
PyChef
# For paste.util.template used in keystone.common.template
Paste

View File

@@ -0,0 +1,19 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from heatclient import Client

View File

@@ -26,24 +26,17 @@ from windc.db import api as db_api
LOG = logging.getLogger(__name__)
class Controller(object):
class Datacenters_Controller(object):
def __init__(self, conf):
LOG.debug("Creating data centers controller with config:"
"datacenters.py %s", conf)
self.conf = conf
@utils.verify_tenant
def findLBforVM(self, req, tenant_id, vm_id):
LOG.debug("Got index request. Request: %s", req)
result = core_api.lb_find_for_vm(self.conf, tenant_id, vm_id)
return {'loadbalancers': result}
@utils.verify_tenant
def index(self, req, tenant_id):
LOG.debug("Got index request. Request: %s", req)
result = core_api.dc_get_index(self.conf, tenant_id)
LOG.debug("Got list of datacenters: %s", result)
result
return {'datacenters': result}
@utils.http_success_code(202)
@@ -80,4 +73,4 @@ def create_resource(conf):
"""Datacenters resource factory method"""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(conf), deserializer, serializer)
return wsgi.Resource(Datacenters_Controller(conf), deserializer, serializer)

View File

@@ -20,10 +20,6 @@ import routes
from windc.api.v1 import datacenters
from windc.api.v1 import services
#from . import tasks
from openstack.common import wsgi
@@ -32,7 +28,7 @@ LOG = logging.getLogger(__name__)
class API(wsgi.Router):
"""WSGI router for balancer v1 API requests."""
"""WSGI router for windc v1 API requests."""
def __init__(self, conf, **local_conf):
self.conf = conf
@@ -41,16 +37,20 @@ class API(wsgi.Router):
datacenter_resource = datacenters.create_resource(self.conf)
datacenter_collection = tenant_mapper.collection(
"datacenters", "datacenter",
controller=datacenter_resource, member_prefix="/{datacenter_id}",
controller=datacenter_resource,
member_prefix="/{datacenter_id}",
formatted=False)
service_resource = services.create_resource(self.conf)
service_collection = datacenter_collection.member.collection('services', 'service',
controller=service_resource, member_prefix="/{service_id}",
formatted=False)
service_collection.member.connect("/{status}", action="changeServiceStatus",
conditions={'method': ["PUT"]})
service_collection = datacenter_collection.member.\
collection('services','service',
controller=service_resource,
member_prefix="/{service_id}",
formatted=False)
service_collection.member.connect("/{status}",
action="changeServiceStatus",
conditions={'method': ["PUT"]})
mapper.connect("/servicetypes",
controller=datacenter_resource,
action="show_servicetypes",
conditions={'method': ["GET"]})
controller=datacenter_resource,
action="show_servicetypes",
conditions={'method': ["GET"]})
super(API, self).__init__(mapper)

View File

@@ -26,22 +26,17 @@ from windc.db import api as db_api
LOG = logging.getLogger(__name__)
class Controller(object):
class Services_Controller(object):
def __init__(self, conf):
LOG.debug("Creating services controller with config:"
"services.py %s", conf)
self.conf = conf
@utils.verify_tenant
def findLBforVM(self, req, tenant_id, vm_id):
LOG.debug("Got index request. Request: %s", req)
result = core_api.lb_find_for_vm(self.conf, tenant_id, vm_id)
return {'loadbalancers': result}
@utils.verify_tenant
def index(self, req, tenant_id, datacenter_id):
LOG.debug("Got index request. Request: %s", req)
result = core_api.service_get_index(self.conf, tenant_id, datacenter_id)
result = core_api.service_get_index(self.conf, tenant_id,
datacenter_id)
return {'services': result}
@utils.http_success_code(202)
@@ -61,19 +56,22 @@ class Controller(object):
@utils.verify_tenant
def delete(self, req, tenant_id, datacenter_id, service_id):
LOG.debug("Got delete request. Request: %s", req)
core_api.delete_service(self.conf, tenant_id, datacenter_id, service_id)
core_api.delete_service(self.conf, tenant_id,
datacenter_id, service_id)
@utils.verify_tenant
def show(self, req, tenant_id, datacenter_id, service_id):
LOG.debug("Got loadbalancerr info request. Request: %s", req)
result = core_api.service_get_data(self.conf, tenant_id, datacenter_id, service_id)
result = core_api.service_get_data(self.conf, tenant_id,
datacenter_id, service_id)
return {'service': result}
@utils.http_success_code(202)
@utils.verify_tenant
def update(self, req, tenant_id, datacenter_id, service_id, body):
LOG.debug("Got update request. Request: %s", req)
core_api.update_service(self.conf, tenant_id, datacenter_id, service_id, body)
core_api.update_service(self.conf, tenant_id, datacenter_id,
service_id, body)
return {'service': {'id': service_id}}
@@ -81,7 +79,4 @@ def create_resource(conf):
"""Services resource factory method"""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(Controller(conf), deserializer, serializer)
return wsgi.Resource(Services_Controller(conf), deserializer, serializer)

View File

@@ -46,7 +46,7 @@ from windc.common import utils
bind_opts = [
cfg.StrOpt('bind_host', default='0.0.0.0'),
cfg.StrOpt('bind_host', default='localhost'),
cfg.IntOpt('bind_port'),
]

View File

@@ -51,19 +51,22 @@ def update_dc(conf, tenant_id, datacenter_id, body):
old_dc = copy.deepcopy(dc)
db_api.pack_update(dc, body)
dc = db_api.datacenter_update(conf, datacenter_id, dc)
event = events.Event(events.SCOPE_DATACENTER_CHANGE, events.ACTION_MODIFY)
event = events.Event(events.SCOPE_DATACENTER_CHANGE,
events.ACTION_MODIFY)
event.previous_state = old_dc
events.change_event(conf, event, dc)
pass
def service_get_index(conf, tenant_id, datacenter_id):
srvcs = db_api.service_get_all_by_datacenter_id(conf, tenant_id, dtacenter_id)
srvcs = db_api.service_get_all_by_datacenter_id(conf, tenant_id,
datacenter_id)
srv_list = [db_api.unpack_extra(srv) for srv in srvcs]
return srv_list
pass
def create_service(conf, params):
# We need to pack all attributes which are not defined by the model explicitly
# We need to pack all attributes which are not defined
# by the model explicitly
srv_params = db_api.service_pack_extra(params)
srv = db_api.service_create(conf, srv_params)
event = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_ADD)
@@ -80,7 +83,7 @@ def delete_service(conf, tenant_id, datacenter_id, service_id):
pass
def service_get_data(conf, tenant_id, datacenter_id, service_id):
srv = db_api.service_get(conf,service_id, tenant_id)
srv = db_api.service_get(conf, service_id, tenant_id)
srv_data = db_api.unpack_extra(srv)
return srv_data
pass
@@ -93,4 +96,4 @@ def update_service(conf, tenant_id, datacenter_id, service_id, body):
event = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_MODIFY)
event.previous_state = old_srv
events.change_event(conf, event, srv)
pass
pass

View File

@@ -29,5 +29,9 @@ class Builder:
def build(self, context, event, data):
pass
def create_context():
context = {}
context['commands']=[]
return context

View File

@@ -17,11 +17,14 @@
import logging
import uuid
LOG = logging.getLogger(__name__)
from windc.core.builder import Builder
from windc.core import change_events as events
from windc.db import api as db_api
from windc.core.templates import Template
from windc.core import commands as command_api
class ActiveDirectory(Builder):
def __init__(self):
@@ -35,6 +38,8 @@ class ActiveDirectory(Builder):
LOG.info ("Got service change event. Analysing..")
if self.do_analysis(context, event, dc):
self.plan_changes(context, event, dc)
self.submit_commands(context, event, dc)
else:
LOG.debug("Not in my scope. Skip event.")
pass
@@ -44,10 +49,66 @@ class ActiveDirectory(Builder):
zones = data['zones']
if data['type'] == self.type and len(zones) == 1:
LOG.debug("It is a service which I should build.")
datacenter_id = data['datacenter_id']
dc = db_api.datacenter_get(context['conf'],data['tenant_id'],
data['datacenter_id'])
datacenter = db_api.unpack_extra(dc)
context['stack_name']=datacenter['name']
return True
else:
return False
def plan_changes(self, context, event, data):
# Here we can plan multiple command execution.
# It might be Heat call command, then chef call command and other
#
LOG.debug("Plan changes...")
self.prepare_template(context, event, data)
self.chef_configuration(context, event, data)
context['commands'].append(self.deploy_template_command(context, event, data))
context['commands'].append(self.chef_configuration_command(context, event, data))
pass
def prepare_template(self, context, event, data):
LOG.debug("Prepare CloudFormation Template...")
template = Template()
template.add_description('Base template for Active Directory deployment')
sec_grp = template.create_security_group('Security group for AD')
rule = template.create_securitygroup_rule('tcp','3389','3389','0.0.0.0/0')
template.add_rule_to_securitygroup(sec_grp, rule)
template.add_resource('ADSecurityGroup', sec_grp)
instance = template.create_instance()
instance_name= 'AD-DC001'
template.add_security_group(instance, 'ADSecurityGroup')
template.add_resource(instance_name, instance)
template.add_output_value(instance_name+'-IP',{"Fn::GetAtt" : [instance_name,'PublicIp']},
'Public IP for the domain controller.')
context['template']=template
pass
def deploy_template_command(self, context, event, data):
LOG.debug("Creating CloudFormation Template deployment command...")
fname = "templates/"+str(uuid.uuid4())
f=open(fname, "w")
f.write(context['template'].to_json())
f.close()
context['template_name']=fname
command = command_api.Command(command_api.TEMPLATE_DEPLOYMENT_COMMAND, context)
return command
pass
def chef_configuration(self, context, event, data):
LOG.debug("Creating Chef configuration...")
context['Role'] = 'pdc'
pass
def chef_configuration_command(self, context, event, data):
LOG.debug("Creating Chef configuration command...")
command = command_api.Command(command_api.CHEF_COMMAND, context)
return command
def submit_commands(self, context, event, data):
LOG.debug("Submit commands for execution...")
pass

View File

@@ -20,6 +20,8 @@ import logging
LOG = logging.getLogger(__name__)
from windc.core import builder_set
from windc.core import builder
from windc.drivers import command_executor
#Declare events types
SCOPE_SERVICE_CHANGE = "Service"
@@ -40,11 +42,14 @@ class Event:
def change_event(conf, event, data):
LOG.info("Change event of type: %s ", event)
context = {}
context = builder.create_context()
context['conf'] = conf
for builder_type in builder_set.builders.set:
builder = builder_set.builders.set[builder_type]
builder.build(context, event, data)
builder_instance = builder_set.builders.set[builder_type]
builder_instance.build(context, event, data)
executor = command_executor.Executor()
executor.execute(context['commands'])
pass

View File

@@ -0,0 +1,43 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
TEMPLATE_DEPLOYMENT_COMMAND = "Template"
CHEF_COMMAND = "Chef"
CHEF_OP_CREATE_ENV = "Env"
CHEF_OP_CREATE_ROLE = "Role"
CHEF_OP_ASSIGN_ROLE = "AssignRole"
CHEF_OP_CREATE_NODE = "CRNode"
class Command:
type = "Empty"
context = None
def __init__(self):
self.type = "Empty"
self.context = None
self.data = None
def __init__(self, type, context):
self.type = type
self.context = context
def __init__(self, type, context, data):
self.type = type
self.context = context
self.data = data

View File

@@ -0,0 +1,107 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
from windc.common.wsgi import JSONResponseSerializer
LOG = logging.getLogger(__name__)
class Template:
def __init__(self):
self.content = {'AWSTemplateFormatVersion':'2010-09-09', 'Description':'',
'Parameters':{}}
self.content['Mappings'] = {
"AWSInstanceType2Arch" : {
"t1.micro" : { "Arch" : "32" },
"m1.small" : { "Arch" : "32" },
"m1.large" : { "Arch" : "64" },
"m1.xlarge" : { "Arch" : "64" },
"m2.xlarge" : { "Arch" : "64" },
"m2.2xlarge" : { "Arch" : "64" },
"m2.4xlarge" : { "Arch" : "64" },
"c1.medium" : { "Arch" : "32" },
"c1.xlarge" : { "Arch" : "64" },
"cc1.4xlarge" : { "Arch" : "64" }
},
"DistroArch2AMI": {
"F16" : { "32" : "F16-i386-cfntools", "64" : "F16-x86_64-cfntools" },
"F17" : { "32" : "F17-i386-cfntools", "64" : "F17-x86_64-cfntools" },
"U10" : { "32" : "U10-i386-cfntools", "64" : "U10-x86_64-cfntools" },
"RHEL-6.1": { "32" : "rhel61-i386-cfntools", "64" : "rhel61-x86_64-cfntools" },
"RHEL-6.2": { "32" : "rhel62-i386-cfntools", "64" : "rhel62-x86_64-cfntools" },
"RHEL-6.3": { "32" : "rhel63-i386-cfntools", "64" : "rhel63-x86_64-cfntools" }
}
}
self.content['Resources'] = {}
self.content['Outputs'] = {}
def to_json(self):
serializer = JSONResponseSerializer()
json = serializer.to_json(self.content)
return json
def empty_template(self):
pass
def add_description(self, description):
self.content['Description'] = description
def add_parameter(self, name, parameter):
self.content['Parameters'].update({name : parameter})
def add_resource(self, name, resource):
self.content['Resources'].update({name : resource})
def create_parameter(self, defult, type, decription):
parameter = {'Default':default, 'Type':type, 'Description':description}
return parameter
def create_security_group(self, description):
sec_grp = {'Type':'AWS::EC2::SecurityGroup'}
sec_grp['Properties'] = {}
sec_grp['Properties']['GroupDescription'] = description
sec_grp['Properties']['SecurityGroupIngress'] = []
return sec_grp
def add_rule_to_securitygroup(self, grp, rule):
grp['Properties']['SecurityGroupIngress'].append(rule)
def create_securitygroup_rule(self, proto, f_port, t_port, cidr):
rule = {'IpProtocol':proto, 'FromPort':f_port, 'ToPort':t_port,'CidrIp': cidr}
return rule
def create_instance(self):
instance = {'Type':'AWS::EC2::Instance','Metadata':{},'Properties':{}}
instance['Properties']['ImageId'] = 'U10-x86_64-cfntools'
instance['Properties']['SecurityGroups']=[]
instance['Properties']['KeyName'] = 'keero-linux-keys'
instance['Properties']['InstanceType'] = 'm1.small'
return instance
def add_security_group(self, instance, grp_name):
instance['Properties']['SecurityGroups'].append({'Ref': grp_name})
def add_output_value(self, name, value, description):
self.content['Outputs'].update({name:{'Value':value, 'Description':description}})
def get_content(self):
return self.content

View File

@@ -88,7 +88,8 @@ def datacenter_create(conf, values):
def datacenter_update(conf, datacenter_id, values):
session = get_session(conf)
with session.begin():
datacenter_ref = datacenter_get(conf, datacenter_id, session=session)
datacenter_ref = session.query(models.DataCenter).\
filter_by(id=datacenter_id).first()
datacenter_ref.update(values)
return datacenter_ref
@@ -96,8 +97,10 @@ def datacenter_update(conf, datacenter_id, values):
def datacenter_destroy(conf, datacenter_id):
session = get_session(conf)
with session.begin():
datacenter_ref = device_get(conf, datacenter_id, session=session)
datacenter_ref = session.query(models.DataCenter).\
filter_by(id=datacenter_id).first()
session.delete(datacenter_ref)
return datacenter_ref
# Service
@@ -112,31 +115,10 @@ def service_get(conf, service_id, tenant_id=None, session=None):
raise exception.ServiceNotFound(service_ref=service_ref)
return service_ref
def service_get_all_by_project(conf, tenant_id):
session = get_session(conf)
query = session.query(models.Service).filter_by(tenant_id=tenant_id)
return query.all()
def service_get_all_by_vm_id(conf, tenant_id, vm_id):
session = get_session(conf)
query = session.query(models.Service).distinct().\
filter_by(tenant_id=tenant_id).\
filter(vm_id == vm_id)
return query.all()
def service_get_all_by_datacenter_id(conf, tenant_id, datacenter_id):
session = get_session(conf)
query = session.query(models.Service).filter_by(datacenter_id=datacenter_id)
service_refs = query.all()
if not service_refs:
raise exception.ServiceNotFound('No service '
'for the datacenter %s found'
% datacenter_id)
return service_refs
return query.all()
def service_create(conf, values):
session = get_session(conf)
@@ -146,7 +128,6 @@ def service_create(conf, values):
session.add(service_ref)
return service_ref
def service_update(conf, service_id, values):
session = get_session(conf)
with session.begin():
@@ -155,13 +136,23 @@ def service_update(conf, service_id, values):
service_ref['updated_at'] = datetime.datetime.utcnow()
return service_ref
def service_destroy(conf, service_id):
session = get_session(conf)
with session.begin():
service_ref = service_get(conf, service_id, session=session)
session.delete(service_ref)
def service_get_all_by_project(conf, tenant_id):
session = get_session(conf)
query = session.query(models.Service).filter_by(tenant_id=tenant_id)
return query.all()
def service_get_all_by_vm_id(conf, tenant_id, vm_id):
session = get_session(conf)
query = session.query(models.Service).distinct().\
filter_by(tenant_id=tenant_id).\
filter(vm_id == vm_id)
return query.all()
def service_count_active_by_datacenter(conf, datacenter_id):
session = get_session(conf)
@@ -171,5 +162,3 @@ def service_count_active_by_datacenter(conf, datacenter_id):
filter_by(status=service_status.ACTIVE).\
count()
return service_count

View File

@@ -0,0 +1,37 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from windc.core import commands as commands_api
from windc.drivers import openstack_heat
class Executor:
map = {commands_api.TEMPLATE_DEPLOYMENT_COMMAND : openstack_heat.Heat}
def __init__(self):
pass
def execute(self, commands):
for command in commands:
if command.type == commands_api.TEMPLATE_DEPLOYMENT_COMMAND:
executor = openstack_heat.Heat()
executor.execute(command)

View File

@@ -0,0 +1,38 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Piston Cloud Computing, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#from heatclient import Client
from subprocess import call
import logging
LOG = logging.getLogger(__name__)
class Heat:
def __init__(self):
pass
def execute(self, command):
# client = Client('1',OS_IMAGE_ENDPOINT, OS_TENANT_ID)
LOG.debug('Calling heat script to execute template')
call(["./heat_run","stack-create","-f "+command.context['template_name'],
command.context['stack_name']])
pass