Merge branch 'master' of ssh://gerrit:29418/keero/keero
This commit is contained in:
BIN
WindowsAgent/Tools/NuGet.exe
Normal file
BIN
WindowsAgent/Tools/NuGet.exe
Normal file
Binary file not shown.
20
WindowsAgent/WindowsAgent.sln
Normal file
20
WindowsAgent/WindowsAgent.sln
Normal 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
|
30
WindowsAgent/WindowsAgent/App.config
Normal file
30
WindowsAgent/WindowsAgent/App.config
Normal 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}: <${logger:shortName=true}> ${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>
|
21
WindowsAgent/WindowsAgent/ExecutionPlan.cs
Normal file
21
WindowsAgent/WindowsAgent/ExecutionPlan.cs
Normal 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; }
|
||||
}
|
||||
}
|
25
WindowsAgent/WindowsAgent/MqMessage.cs
Normal file
25
WindowsAgent/WindowsAgent/MqMessage.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
155
WindowsAgent/WindowsAgent/PlanExecutor.cs
Normal file
155
WindowsAgent/WindowsAgent/PlanExecutor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
97
WindowsAgent/WindowsAgent/Program.cs
Normal file
97
WindowsAgent/WindowsAgent/Program.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
36
WindowsAgent/WindowsAgent/Properties/AssemblyInfo.cs
Normal file
36
WindowsAgent/WindowsAgent/Properties/AssemblyInfo.cs
Normal 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")]
|
122
WindowsAgent/WindowsAgent/RabbitMqClient.cs
Normal file
122
WindowsAgent/WindowsAgent/RabbitMqClient.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
WindowsAgent/WindowsAgent/SampleExecutionPlan.json
Normal file
37
WindowsAgent/WindowsAgent/SampleExecutionPlan.json
Normal 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
|
||||
}
|
111
WindowsAgent/WindowsAgent/ServiceManager.cs
Normal file
111
WindowsAgent/WindowsAgent/ServiceManager.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
92
WindowsAgent/WindowsAgent/WindowsAgent.csproj
Normal file
92
WindowsAgent/WindowsAgent/WindowsAgent.csproj
Normal 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>
|
95
WindowsAgent/WindowsAgent/WindowsService.cs
Normal file
95
WindowsAgent/WindowsAgent/WindowsService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
39
WindowsAgent/WindowsAgent/WindowsServiceInstaller.cs
Normal file
39
WindowsAgent/WindowsAgent/WindowsServiceInstaller.cs
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
6
WindowsAgent/WindowsAgent/packages.config
Normal file
6
WindowsAgent/WindowsAgent/packages.config
Normal 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>
|
4
WindowsAgent/packages/repositories.config
Normal file
4
WindowsAgent/packages/repositories.config
Normal 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
31
dashboard/ReadMe.txt
Normal 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
66
dashboard/api/windc.py
Normal 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)
|
0
dashboard/windc/__init__.py
Normal file
0
dashboard/windc/__init__.py
Normal file
52
dashboard/windc/forms.py
Normal file
52
dashboard/windc/forms.py
Normal 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
29
dashboard/windc/panel.py
Normal 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
140
dashboard/windc/tables.py
Normal 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
38
dashboard/windc/tabs.py
Normal 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
|
2
dashboard/windc/templates/windc/_data_center_help.html
Normal file
2
dashboard/windc/templates/windc/_data_center_help.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{% load i18n %}
|
||||
<p>{% blocktrans %}Data Center is an instance with different services.{% endblocktrans %}</p>
|
2
dashboard/windc/templates/windc/_dc_help.html
Normal file
2
dashboard/windc/templates/windc/_dc_help.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{% load i18n %}
|
||||
<p>{% blocktrans %}You can deploy few domain controllers with one name.{% endblocktrans %}</p>
|
2
dashboard/windc/templates/windc/_iis_help.html
Normal file
2
dashboard/windc/templates/windc/_iis_help.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{% load i18n %}
|
||||
<p>{% blocktrans %}You can deploy few Internet Information Services in one domain.{% endblocktrans %}</p>
|
3
dashboard/windc/templates/windc/_services.html
Normal file
3
dashboard/windc/templates/windc/_services.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<h3>{% trans "Services" %}</h3>
|
11
dashboard/windc/templates/windc/create.html
Normal file
11
dashboard/windc/templates/windc/create.html
Normal 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 %}
|
11
dashboard/windc/templates/windc/create_dc.html
Normal file
11
dashboard/windc/templates/windc/create_dc.html
Normal 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 %}
|
11
dashboard/windc/templates/windc/index.html
Normal file
11
dashboard/windc/templates/windc/index.html
Normal 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 %}
|
11
dashboard/windc/templates/windc/services.html
Normal file
11
dashboard/windc/templates/windc/services.html
Normal 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 %}
|
15
dashboard/windc/templates/windc/services_tabs.html
Normal file
15
dashboard/windc/templates/windc/services_tabs.html
Normal 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 %}
|
11
dashboard/windc/templates/windc/update.html
Normal file
11
dashboard/windc/templates/windc/update.html
Normal 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
34
dashboard/windc/urls.py
Normal 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
102
dashboard/windc/views.py
Normal 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
|
188
dashboard/windc/workflows.py
Normal file
188
dashboard/windc/workflows.py
Normal 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
|
0
dashboard/windcclient/__init__.py
Normal file
0
dashboard/windcclient/__init__.py
Normal file
0
dashboard/windcclient/common/__init__.py
Normal file
0
dashboard/windcclient/common/__init__.py
Normal file
137
dashboard/windcclient/common/base.py
Normal file
137
dashboard/windcclient/common/base.py
Normal 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
|
151
dashboard/windcclient/common/client.py
Normal file
151
dashboard/windcclient/common/client.py
Normal 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)
|
140
dashboard/windcclient/common/exceptions.py
Normal file
140
dashboard/windcclient/common/exceptions.py
Normal 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)
|
62
dashboard/windcclient/common/service_catalog.py
Normal file
62
dashboard/windcclient/common/service_catalog.py
Normal 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.')
|
291
dashboard/windcclient/common/utils.py
Normal file
291
dashboard/windcclient/common/utils.py
Normal 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)
|
285
dashboard/windcclient/shell.py
Normal file
285
dashboard/windcclient/shell.py
Normal 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
|
0
dashboard/windcclient/v1/__init__.py
Normal file
0
dashboard/windcclient/v1/__init__.py
Normal file
29
dashboard/windcclient/v1/client.py
Normal file
29
dashboard/windcclient/v1/client.py
Normal 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)
|
44
dashboard/windcclient/v1/datacenters.py
Normal file
44
dashboard/windcclient/v1/datacenters.py
Normal 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')
|
48
dashboard/windcclient/v1/services.py
Normal file
48
dashboard/windcclient/v1/services.py
Normal 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
5
windc/heat_run
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
source openrc.sh
|
||||
heat "$@"
|
||||
|
@@ -4,5 +4,5 @@
|
||||
"domain": "ACME.cloud",
|
||||
"AdminUser": "Admin",
|
||||
"AdminPassword": "StrongPassword",
|
||||
"DomainControllerNames": ["APP-AD001","APP-AD002"]
|
||||
"DomainControllerNames": ["AD-DC001"]
|
||||
}
|
||||
|
@@ -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
|
||||
|
||||
|
19
windc/windc/adapters/openstack.py
Normal file
19
windc/windc/adapters/openstack.py
Normal 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
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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'),
|
||||
]
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -29,5 +29,9 @@ class Builder:
|
||||
def build(self, context, event, data):
|
||||
pass
|
||||
|
||||
def create_context():
|
||||
context = {}
|
||||
context['commands']=[]
|
||||
return context
|
||||
|
||||
|
||||
|
@@ -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
|
@@ -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
|
||||
|
||||
|
||||
|
43
windc/windc/core/commands.py
Normal file
43
windc/windc/core/commands.py
Normal 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
|
||||
|
||||
|
107
windc/windc/core/templates.py
Normal file
107
windc/windc/core/templates.py
Normal 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
|
||||
|
||||
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
||||
|
37
windc/windc/drivers/command_executor.py
Normal file
37
windc/windc/drivers/command_executor.py
Normal 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)
|
||||
|
||||
|
38
windc/windc/drivers/openstack_heat.py
Normal file
38
windc/windc/drivers/openstack_heat.py
Normal 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
|
||||
|
Reference in New Issue
Block a user