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",
|
"domain": "ACME.cloud",
|
||||||
"AdminUser": "Admin",
|
"AdminUser": "Admin",
|
||||||
"AdminPassword": "StrongPassword",
|
"AdminPassword": "StrongPassword",
|
||||||
"DomainControllerNames": ["APP-AD001","APP-AD002"]
|
"DomainControllerNames": ["AD-DC001"]
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
# package to get the right headers...
|
# package to get the right headers...
|
||||||
greenlet>=0.3.1
|
greenlet>=0.3.1
|
||||||
|
|
||||||
SQLAlchemy>=0.7
|
SQLAlchemy<=0.7.9
|
||||||
anyjson
|
anyjson
|
||||||
eventlet>=0.9.12
|
eventlet>=0.9.12
|
||||||
PasteDeploy
|
PasteDeploy
|
||||||
@@ -15,7 +15,7 @@ sqlalchemy-migrate>=0.7.2
|
|||||||
httplib2
|
httplib2
|
||||||
kombu
|
kombu
|
||||||
iso8601>=0.1.4
|
iso8601>=0.1.4
|
||||||
|
PyChef
|
||||||
# For paste.util.template used in keystone.common.template
|
# For paste.util.template used in keystone.common.template
|
||||||
Paste
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Controller(object):
|
class Datacenters_Controller(object):
|
||||||
def __init__(self, conf):
|
def __init__(self, conf):
|
||||||
LOG.debug("Creating data centers controller with config:"
|
LOG.debug("Creating data centers controller with config:"
|
||||||
"datacenters.py %s", conf)
|
"datacenters.py %s", conf)
|
||||||
self.conf = 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
|
@utils.verify_tenant
|
||||||
def index(self, req, tenant_id):
|
def index(self, req, tenant_id):
|
||||||
LOG.debug("Got index request. Request: %s", req)
|
LOG.debug("Got index request. Request: %s", req)
|
||||||
result = core_api.dc_get_index(self.conf, tenant_id)
|
result = core_api.dc_get_index(self.conf, tenant_id)
|
||||||
LOG.debug("Got list of datacenters: %s", result)
|
LOG.debug("Got list of datacenters: %s", result)
|
||||||
result
|
|
||||||
return {'datacenters': result}
|
return {'datacenters': result}
|
||||||
|
|
||||||
@utils.http_success_code(202)
|
@utils.http_success_code(202)
|
||||||
@@ -80,4 +73,4 @@ def create_resource(conf):
|
|||||||
"""Datacenters resource factory method"""
|
"""Datacenters resource factory method"""
|
||||||
deserializer = wsgi.JSONRequestDeserializer()
|
deserializer = wsgi.JSONRequestDeserializer()
|
||||||
serializer = wsgi.JSONResponseSerializer()
|
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 datacenters
|
||||||
from windc.api.v1 import services
|
from windc.api.v1 import services
|
||||||
|
|
||||||
#from . import tasks
|
|
||||||
|
|
||||||
|
|
||||||
from openstack.common import wsgi
|
from openstack.common import wsgi
|
||||||
|
|
||||||
|
|
||||||
@@ -32,7 +28,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class API(wsgi.Router):
|
class API(wsgi.Router):
|
||||||
|
|
||||||
"""WSGI router for balancer v1 API requests."""
|
"""WSGI router for windc v1 API requests."""
|
||||||
|
|
||||||
def __init__(self, conf, **local_conf):
|
def __init__(self, conf, **local_conf):
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
@@ -41,16 +37,20 @@ class API(wsgi.Router):
|
|||||||
datacenter_resource = datacenters.create_resource(self.conf)
|
datacenter_resource = datacenters.create_resource(self.conf)
|
||||||
datacenter_collection = tenant_mapper.collection(
|
datacenter_collection = tenant_mapper.collection(
|
||||||
"datacenters", "datacenter",
|
"datacenters", "datacenter",
|
||||||
controller=datacenter_resource, member_prefix="/{datacenter_id}",
|
controller=datacenter_resource,
|
||||||
|
member_prefix="/{datacenter_id}",
|
||||||
formatted=False)
|
formatted=False)
|
||||||
service_resource = services.create_resource(self.conf)
|
service_resource = services.create_resource(self.conf)
|
||||||
service_collection = datacenter_collection.member.collection('services', 'service',
|
service_collection = datacenter_collection.member.\
|
||||||
controller=service_resource, member_prefix="/{service_id}",
|
collection('services','service',
|
||||||
formatted=False)
|
controller=service_resource,
|
||||||
service_collection.member.connect("/{status}", action="changeServiceStatus",
|
member_prefix="/{service_id}",
|
||||||
conditions={'method': ["PUT"]})
|
formatted=False)
|
||||||
|
service_collection.member.connect("/{status}",
|
||||||
|
action="changeServiceStatus",
|
||||||
|
conditions={'method': ["PUT"]})
|
||||||
mapper.connect("/servicetypes",
|
mapper.connect("/servicetypes",
|
||||||
controller=datacenter_resource,
|
controller=datacenter_resource,
|
||||||
action="show_servicetypes",
|
action="show_servicetypes",
|
||||||
conditions={'method': ["GET"]})
|
conditions={'method': ["GET"]})
|
||||||
super(API, self).__init__(mapper)
|
super(API, self).__init__(mapper)
|
||||||
|
@@ -26,22 +26,17 @@ from windc.db import api as db_api
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Controller(object):
|
class Services_Controller(object):
|
||||||
def __init__(self, conf):
|
def __init__(self, conf):
|
||||||
LOG.debug("Creating services controller with config:"
|
LOG.debug("Creating services controller with config:"
|
||||||
"services.py %s", conf)
|
"services.py %s", conf)
|
||||||
self.conf = 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
|
@utils.verify_tenant
|
||||||
def index(self, req, tenant_id, datacenter_id):
|
def index(self, req, tenant_id, datacenter_id):
|
||||||
LOG.debug("Got index request. Request: %s", req)
|
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}
|
return {'services': result}
|
||||||
|
|
||||||
@utils.http_success_code(202)
|
@utils.http_success_code(202)
|
||||||
@@ -61,19 +56,22 @@ class Controller(object):
|
|||||||
@utils.verify_tenant
|
@utils.verify_tenant
|
||||||
def delete(self, req, tenant_id, datacenter_id, service_id):
|
def delete(self, req, tenant_id, datacenter_id, service_id):
|
||||||
LOG.debug("Got delete request. Request: %s", req)
|
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
|
@utils.verify_tenant
|
||||||
def show(self, req, tenant_id, datacenter_id, service_id):
|
def show(self, req, tenant_id, datacenter_id, service_id):
|
||||||
LOG.debug("Got loadbalancerr info request. Request: %s", req)
|
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}
|
return {'service': result}
|
||||||
|
|
||||||
@utils.http_success_code(202)
|
@utils.http_success_code(202)
|
||||||
@utils.verify_tenant
|
@utils.verify_tenant
|
||||||
def update(self, req, tenant_id, datacenter_id, service_id, body):
|
def update(self, req, tenant_id, datacenter_id, service_id, body):
|
||||||
LOG.debug("Got update request. Request: %s", req)
|
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}}
|
return {'service': {'id': service_id}}
|
||||||
|
|
||||||
|
|
||||||
@@ -81,7 +79,4 @@ def create_resource(conf):
|
|||||||
"""Services resource factory method"""
|
"""Services resource factory method"""
|
||||||
deserializer = wsgi.JSONRequestDeserializer()
|
deserializer = wsgi.JSONRequestDeserializer()
|
||||||
serializer = wsgi.JSONResponseSerializer()
|
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 = [
|
bind_opts = [
|
||||||
cfg.StrOpt('bind_host', default='0.0.0.0'),
|
cfg.StrOpt('bind_host', default='localhost'),
|
||||||
cfg.IntOpt('bind_port'),
|
cfg.IntOpt('bind_port'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -51,19 +51,22 @@ def update_dc(conf, tenant_id, datacenter_id, body):
|
|||||||
old_dc = copy.deepcopy(dc)
|
old_dc = copy.deepcopy(dc)
|
||||||
db_api.pack_update(dc, body)
|
db_api.pack_update(dc, body)
|
||||||
dc = db_api.datacenter_update(conf, datacenter_id, dc)
|
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
|
event.previous_state = old_dc
|
||||||
events.change_event(conf, event, dc)
|
events.change_event(conf, event, dc)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def service_get_index(conf, tenant_id, datacenter_id):
|
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]
|
srv_list = [db_api.unpack_extra(srv) for srv in srvcs]
|
||||||
return srv_list
|
return srv_list
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def create_service(conf, params):
|
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_params = db_api.service_pack_extra(params)
|
||||||
srv = db_api.service_create(conf, srv_params)
|
srv = db_api.service_create(conf, srv_params)
|
||||||
event = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_ADD)
|
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
|
pass
|
||||||
|
|
||||||
def service_get_data(conf, tenant_id, datacenter_id, service_id):
|
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)
|
srv_data = db_api.unpack_extra(srv)
|
||||||
return srv_data
|
return srv_data
|
||||||
pass
|
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 = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_MODIFY)
|
||||||
event.previous_state = old_srv
|
event.previous_state = old_srv
|
||||||
events.change_event(conf, event, srv)
|
events.change_event(conf, event, srv)
|
||||||
pass
|
pass
|
||||||
|
@@ -29,5 +29,9 @@ class Builder:
|
|||||||
def build(self, context, event, data):
|
def build(self, context, event, data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def create_context():
|
||||||
|
context = {}
|
||||||
|
context['commands']=[]
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@@ -17,11 +17,14 @@
|
|||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import uuid
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
from windc.core.builder import Builder
|
from windc.core.builder import Builder
|
||||||
from windc.core import change_events as events
|
from windc.core import change_events as events
|
||||||
from windc.db import api as db_api
|
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):
|
class ActiveDirectory(Builder):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -35,6 +38,8 @@ class ActiveDirectory(Builder):
|
|||||||
LOG.info ("Got service change event. Analysing..")
|
LOG.info ("Got service change event. Analysing..")
|
||||||
if self.do_analysis(context, event, dc):
|
if self.do_analysis(context, event, dc):
|
||||||
self.plan_changes(context, event, dc)
|
self.plan_changes(context, event, dc)
|
||||||
|
|
||||||
|
self.submit_commands(context, event, dc)
|
||||||
else:
|
else:
|
||||||
LOG.debug("Not in my scope. Skip event.")
|
LOG.debug("Not in my scope. Skip event.")
|
||||||
pass
|
pass
|
||||||
@@ -44,10 +49,66 @@ class ActiveDirectory(Builder):
|
|||||||
zones = data['zones']
|
zones = data['zones']
|
||||||
if data['type'] == self.type and len(zones) == 1:
|
if data['type'] == self.type and len(zones) == 1:
|
||||||
LOG.debug("It is a service which I should build.")
|
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
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def plan_changes(self, context, event, data):
|
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
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
from windc.core import builder_set
|
from windc.core import builder_set
|
||||||
|
from windc.core import builder
|
||||||
|
from windc.drivers import command_executor
|
||||||
#Declare events types
|
#Declare events types
|
||||||
|
|
||||||
SCOPE_SERVICE_CHANGE = "Service"
|
SCOPE_SERVICE_CHANGE = "Service"
|
||||||
@@ -40,11 +42,14 @@ class Event:
|
|||||||
|
|
||||||
def change_event(conf, event, data):
|
def change_event(conf, event, data):
|
||||||
LOG.info("Change event of type: %s ", event)
|
LOG.info("Change event of type: %s ", event)
|
||||||
context = {}
|
context = builder.create_context()
|
||||||
context['conf'] = conf
|
context['conf'] = conf
|
||||||
for builder_type in builder_set.builders.set:
|
for builder_type in builder_set.builders.set:
|
||||||
builder = builder_set.builders.set[builder_type]
|
builder_instance = builder_set.builders.set[builder_type]
|
||||||
builder.build(context, event, data)
|
builder_instance.build(context, event, data)
|
||||||
|
|
||||||
|
executor = command_executor.Executor()
|
||||||
|
executor.execute(context['commands'])
|
||||||
pass
|
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):
|
def datacenter_update(conf, datacenter_id, values):
|
||||||
session = get_session(conf)
|
session = get_session(conf)
|
||||||
with session.begin():
|
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)
|
datacenter_ref.update(values)
|
||||||
return datacenter_ref
|
return datacenter_ref
|
||||||
|
|
||||||
@@ -96,8 +97,10 @@ def datacenter_update(conf, datacenter_id, values):
|
|||||||
def datacenter_destroy(conf, datacenter_id):
|
def datacenter_destroy(conf, datacenter_id):
|
||||||
session = get_session(conf)
|
session = get_session(conf)
|
||||||
with session.begin():
|
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)
|
session.delete(datacenter_ref)
|
||||||
|
return datacenter_ref
|
||||||
|
|
||||||
# Service
|
# Service
|
||||||
|
|
||||||
@@ -112,31 +115,10 @@ def service_get(conf, service_id, tenant_id=None, session=None):
|
|||||||
raise exception.ServiceNotFound(service_ref=service_ref)
|
raise exception.ServiceNotFound(service_ref=service_ref)
|
||||||
return 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):
|
def service_get_all_by_datacenter_id(conf, tenant_id, datacenter_id):
|
||||||
session = get_session(conf)
|
session = get_session(conf)
|
||||||
query = session.query(models.Service).filter_by(datacenter_id=datacenter_id)
|
query = session.query(models.Service).filter_by(datacenter_id=datacenter_id)
|
||||||
service_refs = query.all()
|
return query.all()
|
||||||
if not service_refs:
|
|
||||||
raise exception.ServiceNotFound('No service '
|
|
||||||
'for the datacenter %s found'
|
|
||||||
% datacenter_id)
|
|
||||||
return service_refs
|
|
||||||
|
|
||||||
|
|
||||||
def service_create(conf, values):
|
def service_create(conf, values):
|
||||||
session = get_session(conf)
|
session = get_session(conf)
|
||||||
@@ -146,7 +128,6 @@ def service_create(conf, values):
|
|||||||
session.add(service_ref)
|
session.add(service_ref)
|
||||||
return service_ref
|
return service_ref
|
||||||
|
|
||||||
|
|
||||||
def service_update(conf, service_id, values):
|
def service_update(conf, service_id, values):
|
||||||
session = get_session(conf)
|
session = get_session(conf)
|
||||||
with session.begin():
|
with session.begin():
|
||||||
@@ -155,13 +136,23 @@ def service_update(conf, service_id, values):
|
|||||||
service_ref['updated_at'] = datetime.datetime.utcnow()
|
service_ref['updated_at'] = datetime.datetime.utcnow()
|
||||||
return service_ref
|
return service_ref
|
||||||
|
|
||||||
|
|
||||||
def service_destroy(conf, service_id):
|
def service_destroy(conf, service_id):
|
||||||
session = get_session(conf)
|
session = get_session(conf)
|
||||||
with session.begin():
|
with session.begin():
|
||||||
service_ref = service_get(conf, service_id, session=session)
|
service_ref = service_get(conf, service_id, session=session)
|
||||||
session.delete(service_ref)
|
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):
|
def service_count_active_by_datacenter(conf, datacenter_id):
|
||||||
session = get_session(conf)
|
session = get_session(conf)
|
||||||
@@ -171,5 +162,3 @@ def service_count_active_by_datacenter(conf, datacenter_id):
|
|||||||
filter_by(status=service_status.ACTIVE).\
|
filter_by(status=service_status.ACTIVE).\
|
||||||
count()
|
count()
|
||||||
return service_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