Merge remote-tracking branch 'origin/master'

This commit is contained in:
acepace 2016-05-29 23:12:07 +03:00
commit 2f1e690c21
8 changed files with 233 additions and 856 deletions

View File

@ -1,810 +0,0 @@
.vis .overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* Must be displayed above for example selected Timeline items */
z-index: 10;
}
.vis-active {
box-shadow: 0 0 10px #86d5f8;
}
/* override some bootstrap styles screwing up the timelines css */
.vis [class*="span"] {
min-height: 0;
width: auto;
}
.vis.timeline {
}
.vis.timeline.root {
position: relative;
border: 1px solid #bfbfbf;
overflow: hidden;
padding: 0;
margin: 0;
box-sizing: border-box;
}
.vis.timeline .vispanel {
position: absolute;
padding: 0;
margin: 0;
box-sizing: border-box;
}
.vis.timeline .vispanel.center,
.vis.timeline .vispanel.left,
.vis.timeline .vispanel.right,
.vis.timeline .vispanel.top,
.vis.timeline .vispanel.bottom {
border: 1px #bfbfbf;
}
.vis.timeline .vispanel.center,
.vis.timeline .vispanel.left,
.vis.timeline .vispanel.right {
border-top-style: solid;
border-bottom-style: solid;
overflow: hidden;
}
.vis.timeline .vispanel.center,
.vis.timeline .vispanel.top,
.vis.timeline .vispanel.bottom {
border-left-style: solid;
border-right-style: solid;
}
.vis.timeline .background {
overflow: hidden;
}
.vis.timeline .vispanel > .content {
position: relative;
}
.vis.timeline .vispanel .shadow {
position: absolute;
width: 100%;
height: 1px;
box-shadow: 0 0 10px rgba(0,0,0,0.8);
/* TODO: find a nice way to ensure shadows are drawn on top of items
z-index: 1;
*/
}
.vis.timeline .vispanel .shadow.top {
top: -1px;
left: 0;
}
.vis.timeline .vispanel .shadow.bottom {
bottom: -1px;
left: 0;
}
.vis.timeline .labelset {
position: relative;
overflow: hidden;
box-sizing: border-box;
}
.vis.timeline .labelset .vlabel {
position: relative;
left: 0;
top: 0;
width: 100%;
color: #4d4d4d;
box-sizing: border-box;
}
.vis.timeline .labelset .vlabel {
border-bottom: 1px solid #bfbfbf;
}
.vis.timeline .labelset .vlabel:last-child {
border-bottom: none;
}
.vis.timeline .labelset .vlabel .inner {
display: inline-block;
padding: 5px;
}
.vis.timeline .labelset .vlabel .inner.hidden {
padding: 0;
}
.vis.timeline .itemset {
position: relative;
padding: 0;
margin: 0;
box-sizing: border-box;
}
.vis.timeline .itemset .background,
.vis.timeline .itemset .foreground {
position: absolute;
width: 100%;
height: 100%;
overflow: visible;
}
.vis.timeline .axis {
position: absolute;
width: 100%;
height: 0;
left: 0;
z-index: 1;
}
.vis.timeline .foreground .group {
position: relative;
box-sizing: border-box;
border-bottom: 1px solid #bfbfbf;
}
.vis.timeline .foreground .group:last-child {
border-bottom: none;
}
.vis.timeline .item {
position: absolute;
color: #1A1A1A;
border-color: #97B0F8;
border-width: 1px;
background-color: #D5DDF6;
display: inline-block;
padding: 5px;
}
.vis.timeline .item.selected {
border-color: #FFC200;
background-color: #FFF785;
/* z-index must be higher than the z-index of custom time bar and current time bar */
z-index: 2;
}
.vis.timeline .editable .item.selected {
cursor: move;
}
.vis.timeline .item.point.selected {
background-color: #FFF785;
}
.vis.timeline .item.box {
text-align: center;
border-style: solid;
border-radius: 2px;
}
.vis.timeline .item.point {
background: none;
}
.vis.timeline .item.dot {
position: absolute;
padding: 0;
border-width: 4px;
border-style: solid;
border-radius: 4px;
}
.vis.timeline .item.range {
border-style: solid;
border-radius: 2px;
box-sizing: border-box;
}
.vis.timeline .item.background {
overflow: hidden;
border: none;
background-color: rgba(213, 221, 246, 0.4);
box-sizing: border-box;
padding: 0;
margin: 0;
}
.vis.timeline .item.range .content {
position: relative;
display: inline-block;
max-width: 100%;
overflow: hidden;
}
.vis.timeline .item.background .content {
position: absolute;
display: inline-block;
overflow: hidden;
max-width: 100%;
margin: 5px;
}
.vis.timeline .item.line {
padding: 0;
position: absolute;
width: 0;
border-left-width: 1px;
border-left-style: solid;
}
.vis.timeline .item .content {
white-space: nowrap;
overflow: hidden;
}
.vis.timeline .item .delete {
background: url('img/timeline/delete.png') no-repeat top center;
position: absolute;
width: 24px;
height: 24px;
top: 0;
right: -24px;
cursor: pointer;
}
.vis.timeline .item.range .drag-left {
position: absolute;
width: 24px;
max-width: 20%;
height: 100%;
top: 0;
left: -4px;
cursor: w-resize;
}
.vis.timeline .item.range .drag-right {
position: absolute;
width: 24px;
max-width: 20%;
height: 100%;
top: 0;
right: -4px;
cursor: e-resize;
}
.vis.timeline .timeaxis {
position: relative;
overflow: hidden;
}
.vis.timeline .timeaxis.foreground {
top: 0;
left: 0;
width: 100%;
}
.vis.timeline .timeaxis.background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.vis.timeline .timeaxis .text {
position: absolute;
color: #4d4d4d;
padding: 3px;
white-space: nowrap;
}
.vis.timeline .timeaxis .text.measure {
position: absolute;
padding-left: 0;
padding-right: 0;
margin-left: 0;
margin-right: 0;
visibility: hidden;
}
.vis.timeline .timeaxis .grid.vertical {
position: absolute;
border-left: 1px solid;
}
.vis.timeline .timeaxis .grid.minor {
border-color: #e5e5e5;
}
.vis.timeline .timeaxis .grid.major {
border-color: #bfbfbf;
}
.vis.timeline .currenttime {
background-color: #FF7F6E;
width: 2px;
z-index: 1;
}
.vis.timeline .customtime {
background-color: #6E94FF;
width: 2px;
cursor: move;
z-index: 1;
}
.vis.timeline.root {
/*
-webkit-transition: height .4s ease-in-out;
transition: height .4s ease-in-out;
*/
}
.vis.timeline .vispanel {
/*
-webkit-transition: height .4s ease-in-out, top .4s ease-in-out;
transition: height .4s ease-in-out, top .4s ease-in-out;
*/
}
.vis.timeline .axis {
/*
-webkit-transition: top .4s ease-in-out;
transition: top .4s ease-in-out;
*/
}
/* TODO: get animation working nicely
.vis.timeline .item {
-webkit-transition: top .4s ease-in-out;
transition: top .4s ease-in-out;
}
.vis.timeline .item.line {
-webkit-transition: height .4s ease-in-out, top .4s ease-in-out;
transition: height .4s ease-in-out, top .4s ease-in-out;
}
/**/
.vis.timeline .vispanel.background.horizontal .grid.horizontal {
position: absolute;
width: 100%;
height: 0;
border-bottom: 1px solid;
}
.vis.timeline .vispanel.background.horizontal .grid.minor {
border-color: #e5e5e5;
}
.vis.timeline .vispanel.background.horizontal .grid.major {
border-color: #bfbfbf;
}
.vis.timeline .dataaxis .yAxis.major {
width: 100%;
position: absolute;
color: #4d4d4d;
white-space: nowrap;
}
.vis.timeline .dataaxis .yAxis.major.measure{
padding: 0px 0px 0px 0px;
margin: 0px 0px 0px 0px;
border: 0px;
visibility: hidden;
width: auto;
}
.vis.timeline .dataaxis .yAxis.minor{
position: absolute;
width: 100%;
color: #bebebe;
white-space: nowrap;
}
.vis.timeline .dataaxis .yAxis.minor.measure{
padding: 0px 0px 0px 0px;
margin: 0px 0px 0px 0px;
border: 0px;
visibility: hidden;
width: auto;
}
.vis.timeline .dataaxis .yAxis.title{
position: absolute;
color: #4d4d4d;
white-space: nowrap;
bottom: 20px;
text-align: center;
}
.vis.timeline .dataaxis .yAxis.title.measure{
padding: 0px 0px 0px 0px;
margin: 0px 0px 0px 0px;
visibility: hidden;
width: auto;
}
.vis.timeline .dataaxis .yAxis.title.left {
bottom: 0px;
-webkit-transform-origin: left top;
-moz-transform-origin: left top;
-ms-transform-origin: left top;
-o-transform-origin: left top;
transform-origin: left bottom;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
}
.vis.timeline .dataaxis .yAxis.title.right {
bottom: 0px;
-webkit-transform-origin: right bottom;
-moz-transform-origin: right bottom;
-ms-transform-origin: right bottom;
-o-transform-origin: right bottom;
transform-origin: right bottom;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-ms-transform: rotate(90deg);
-o-transform: rotate(90deg);
transform: rotate(90deg);
}
.vis.timeline .legend {
background-color: rgba(247, 252, 255, 0.65);
padding: 5px;
border-color: #b3b3b3;
border-style:solid;
border-width: 1px;
box-shadow: 2px 2px 10px rgba(154, 154, 154, 0.55);
}
.vis.timeline .legendText {
/*font-size: 10px;*/
white-space: nowrap;
display: inline-block
}
.vis.timeline .graphGroup0 {
fill:#4f81bd;
fill-opacity:0;
stroke-width:2px;
stroke: #4f81bd;
}
.vis.timeline .graphGroup1 {
fill:#f79646;
fill-opacity:0;
stroke-width:2px;
stroke: #f79646;
}
.vis.timeline .graphGroup2 {
fill: #8c51cf;
fill-opacity:0;
stroke-width:2px;
stroke: #8c51cf;
}
.vis.timeline .graphGroup3 {
fill: #75c841;
fill-opacity:0;
stroke-width:2px;
stroke: #75c841;
}
.vis.timeline .graphGroup4 {
fill: #ff0100;
fill-opacity:0;
stroke-width:2px;
stroke: #ff0100;
}
.vis.timeline .graphGroup5 {
fill: #37d8e6;
fill-opacity:0;
stroke-width:2px;
stroke: #37d8e6;
}
.vis.timeline .graphGroup6 {
fill: #042662;
fill-opacity:0;
stroke-width:2px;
stroke: #042662;
}
.vis.timeline .graphGroup7 {
fill:#00ff26;
fill-opacity:0;
stroke-width:2px;
stroke: #00ff26;
}
.vis.timeline .graphGroup8 {
fill:#ff00ff;
fill-opacity:0;
stroke-width:2px;
stroke: #ff00ff;
}
.vis.timeline .graphGroup9 {
fill: #8f3938;
fill-opacity:0;
stroke-width:2px;
stroke: #8f3938;
}
.vis.timeline .fill {
fill-opacity:0.1;
stroke: none;
}
.vis.timeline .bar {
fill-opacity:0.5;
stroke-width:1px;
}
.vis.timeline .point {
stroke-width:2px;
fill-opacity:1.0;
}
.vis.timeline .legendBackground {
stroke-width:1px;
fill-opacity:0.9;
fill: #ffffff;
stroke: #c2c2c2;
}
.vis.timeline .outline {
stroke-width:1px;
fill-opacity:1;
fill: #ffffff;
stroke: #e5e5e5;
}
.vis.timeline .iconFill {
fill-opacity:0.3;
stroke: none;
}
div.network-manipulationDiv {
border-width: 0;
border-bottom: 1px;
border-style:solid;
border-color: #d6d9d8;
background: #ffffff; /* Old browsers */
background: -moz-linear-gradient(top, #ffffff 0%, #fcfcfc 48%, #fafafa 50%, #fcfcfc 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(48%,#fcfcfc), color-stop(50%,#fafafa), color-stop(100%,#fcfcfc)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* IE10+ */
background: linear-gradient(to bottom, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc',GradientType=0 ); /* IE6-9 */
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 30px;
}
div.network-manipulation-editMode {
position:absolute;
left: 0;
top: 15px;
height: 30px;
}
div.network-manipulation-closeDiv {
position:absolute;
right: 0;
top: 0;
width: 30px;
height: 30px;
background-position: 20px 3px;
background-repeat: no-repeat;
background-image: url("img/network/cross.png");
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
div.network-manipulation-closeDiv:hover {
opacity: 0.6;
}
div.network-manipulationUI {
position:relative;
top:-7px;
font-family: verdana;
font-size: 12px;
-moz-border-radius: 15px;
border-radius: 15px;
display:inline-block;
background-position: 0px 0px;
background-repeat:no-repeat;
height:24px;
margin: 0px 0px 0px 10px;
vertical-align:middle;
cursor: pointer;
padding: 0px 8px 0px 8px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
div.network-manipulationUI:hover {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.20);
}
div.network-manipulationUI:active {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.50);
}
div.network-manipulationUI.back {
background-image: url("img/network/backIcon.png");
}
div.network-manipulationUI.none:hover {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0);
cursor: default;
}
div.network-manipulationUI.none:active {
box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0);
}
div.network-manipulationUI.none {
padding: 0;
}
div.network-manipulationUI.notification{
margin: 2px;
font-weight: bold;
}
div.network-manipulationUI.add {
background-image: url("img/network/addNodeIcon.png");
}
div.network-manipulationUI.edit {
background-image: url("img/network/editIcon.png");
}
div.network-manipulationUI.edit.editmode {
background-color: #fcfcfc;
border-style:solid;
border-width:1px;
border-color: #cccccc;
}
div.network-manipulationUI.connect {
background-image: url("img/network/connectIcon.png");
}
div.network-manipulationUI.delete {
background-image: url("img/network/deleteIcon.png");
}
/* top right bottom left */
div.network-manipulationLabel {
margin: 0px 0px 0px 23px;
line-height: 25px;
}
div.network-seperatorLine {
display:inline-block;
width:1px;
height:20px;
background-color: #bdbdbd;
margin: 5px 7px 0px 15px;
}
div.network-navigation_wrapper {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
div.network-navigation {
width:34px;
height:34px;
-moz-border-radius: 17px;
border-radius: 17px;
position:absolute;
display:inline-block;
background-position: 2px 2px;
background-repeat:no-repeat;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
div.network-navigation:hover {
box-shadow: 0px 0px 3px 3px rgba(56, 207, 21, 0.30);
}
div.network-navigation:active {
box-shadow: 0px 0px 1px 3px rgba(56, 207, 21, 0.95);
}
div.network-navigation.up {
background-image: url("img/network/upArrow.png");
bottom:50px;
left:55px;
}
div.network-navigation.down {
background-image: url("img/network/downArrow.png");
bottom:10px;
left:55px;
}
div.network-navigation.left {
background-image: url("img/network/leftArrow.png");
bottom:10px;
left:15px;
}
div.network-navigation.right {
background-image: url("img/network/rightArrow.png");
bottom:10px;
left:95px;
}
div.network-navigation.zoomIn {
background-image: url("img/network/plus.png");
bottom:10px;
right:15px;
}
div.network-navigation.zoomOut {
background-image: url("img/network/minus.png");
bottom:10px;
right:55px;
}
div.network-navigation.zoomExtends {
background-image: url("img/network/zoomExtends.png");
bottom:50px;
right:15px;
}
div.network-tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
white-space: nowrap;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
border: 1px solid;
box-shadow: 3px 3px 10px rgba(128, 128, 128, 0.5);
}

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,6 @@
<script type="text/javascript" src="./js/jquery.dataTables.min.js"></script> <script type="text/javascript" src="./js/jquery.dataTables.min.js"></script>
<!-- css --> <!-- css -->
<link type="text/css" href="./css/vis.min.css" rel="stylesheet"/>
<link type="text/css" href="./css/monkeys-admin.css" rel="stylesheet"/> <link type="text/css" href="./css/monkeys-admin.css" rel="stylesheet"/>
<link type="text/css" href="./css/typeahead.css" rel="stylesheet"/> <link type="text/css" href="./css/typeahead.css" rel="stylesheet"/>
<link type="text/css" href="./css/bootstrap.min.css" rel="stylesheet"/> <link type="text/css" href="./css/bootstrap.min.css" rel="stylesheet"/>
@ -68,14 +67,24 @@
<a href="#options" data-toggle="collapse">Options</a> <a href="#options" data-toggle="collapse">Options</a>
</div> </div>
<div id="options" class="panel-body panel-collapse collapse in"> <div id="options" class="panel-body panel-collapse collapse in">
<button id="btnCreateJob" class="btn btn-default" type="button" <span class="input-group-btn">
onclick="createNewJob()" style="margin-top:-4px"> <button id="btnCreateJob" class="btn btn-default" type="button"
Create new scenario onclick="createNewJob()" style="margin-top:-4px">
</button> Create new scenario
</button>
<button id="btnConfigSched" class="btn btn-default" type="button"
onclick="configSched()" style="margin-top:-4px">
Configure Auto Tester
</button>
</span>
<!-- General options --> <!-- General options -->
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<a href="#general" data-toggle="collapse">General</a> <a href="#newJob" data-toggle="collapse">New Job</a>
</div>
<div id="newJob" style="overflow: visible" class="panel-body panel-collapse collapse in" aria-expanded="true">
<div id="job-config">
</div>
</div> </div>
</div> </div>
<!-- /.General options --> <!-- /.General options -->
@ -115,8 +124,7 @@
Update Update
</button>--> </button>-->
</span> </span>
<div style="display: none;" id="job-config">
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -96,26 +96,6 @@ function initAdmin() {
startval: $, startval: $,
}); });
jobCfg = new JSONEditor(document.getElementById('job-config'),{
schema: {
type: "object",
title: "Job",
properties: {
vlan: {
title: "Vlan",
type: "integer",
},
},
options: {
"collapsed": true
},
},
disable_edit_json: false,
});
window.setTimeout(updateJobs, 10000); window.setTimeout(updateJobs, 10000);
loadVcenterConfig(); loadVcenterConfig();
updateJobs(); updateJobs();
@ -141,14 +121,14 @@ function updateJobs() {
} }
function loadVcenterConfig() { function loadVcenterConfig() {
$.getJSON('/connector?type=vcenter', function(json) { $.getJSON('/connector?type=VCenterConnector', function(json) {
vcenterCfg.setValue(json); vcenterCfg.setValue(json);
}); });
} }
function updateVcenterConfig() { function updateVcenterConfig() {
var vc_config = vcenterCfg.getValue() var vc_config = vcenterCfg.getValue()
vc_config["type"] = "vcenter"; vc_config["type"] = "VCenterConnector";
$.ajax({ $.ajax({
headers : { headers : {
@ -173,6 +153,30 @@ function updateVcenterConfig() {
} }
function createNewJob() { function createNewJob() {
elem = document.getElementById('job-config');
elem.innerHTML = ""
jobCfg = new JSONEditor(elem,{
schema: {
type: "object",
title: "Job",
properties: {
job: {
title: "Type",
$ref: "/jobcreate",
}
},
options: {
"collapsed": false
},
},
ajax: true,
disable_edit_json: false,
disable_collapse: true,
disable_properties: true,
});
}
function configSched() {
} }

View File

@ -1,7 +1,7 @@
class NetControllerConnector(object): class NetControllerConnector(object):
def __init__(self): def __init__(self):
_properties = {} self._properties = {}
def _load_prop_dict(self, target, prop): def _load_prop_dict(self, target, prop):
for property in prop: for property in prop:
@ -12,6 +12,9 @@ class NetControllerConnector(object):
else: else:
target[property] = prop[property] target[property] = prop[property]
def is_connected(self):
return False
def connect(self): def connect(self):
return return
@ -33,3 +36,19 @@ class NetControllerConnector(object):
def disconnect(self): def disconnect(self):
return return
class NetControllerJob(object):
connector = NetControllerConnector
def __init__(self):
self._properties = {
# property: [value, enumerating_function]
}
def get_job_properties(self):
return self._properties
def set_job_properties(self, properties):
return {}
def run(self):
raise NotImplementedError()

View File

@ -0,0 +1,43 @@
from connectors import NetControllerJob, NetControllerConnector
demo_state = {
501: ["Machine A", "Machine B"],
502: ["Machine C",],
503: ["Machine D",],
514: ["Machine E", "Machine F"],
}
class DemoConnector(NetControllerConnector):
def __init__(self):
self._conn = None
self._properties = {
"address": "127.0.0.1",
"port": 0,
"username": "",
"password": "",
}
def connect(self):
self._conn = object()
def is_connected(self):
return not self._conn == None
def disconnect(self):
self._conn = None
def get_vlans_list(self):
return demo_state.keys()
def get_entities_on_vlan(self, vlanid):
if (demo_state.has_key(vlanid)):
return demo_state[vlanid]
return []
class DemoJob(NetControllerJob):
connector = DemoConnector
def __init__(self):
self._properties = {
"vlan": [0, "get_vlans_list"],
}

View File

@ -1,8 +1,7 @@
from connectors import NetControllerConnector from connectors import NetControllerJob, NetControllerConnector
from pyVmomi import vim from pyVmomi import vim
from pyVim.connect import SmartConnect, Disconnect from pyVim.connect import SmartConnect, Disconnect
class VCenterConnector(NetControllerConnector): class VCenterConnector(NetControllerConnector):
def __init__(self): def __init__(self):
self._service_instance = None self._service_instance = None
@ -21,15 +20,45 @@ class VCenterConnector(NetControllerConnector):
"resource_pool": "" "resource_pool": ""
} }
} }
self._cache = {
"vlans" : []
}
def connect(self): def connect(self):
self._service_instance = SmartConnect(host=self._address, import ssl
port=self._port, try:
user=self._username, self._service_instance = SmartConnect(host=self._properties["address"],
pwd=self._password) port=self._properties["port"],
user=self._properties["username"],
pwd=self._properties["password"])
except ssl.SSLError:
# some organizations use self-signed certificates...
gcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
self._service_instance = SmartConnect(host=self._properties["address"],
port=self._properties["port"],
user=self._properties["username"],
pwd=self._properties["password"],
sslContext=gcontext)
def is_connected(self):
if (self._service_instance == None):
return False
try:
self._service_instance.serverClock
except vim.fault.NotAuthenticated, e:
return False
def get_vlans_list(self): def get_vlans_list(self):
return [] if not self.is_connected():
self.connect()
if self._cache and self._cache.has_key("vlans") and self._cache["vlans"]:
return self._cache["vlans"]
vcontent = self._service_instance.RetrieveContent() # get updated vsphare state
vimtype = [vim.Network]
objview = vcontent.viewManager.CreateContainerView(vcontent.rootFolder, vimtype, True)
self._cache["vlans"] = [x.name for x in objview.view]
objview.Destroy()
return self._cache["vlans"]
def get_entities_on_vlan(self, vlanid): def get_entities_on_vlan(self, vlanid):
return [] return []
@ -121,3 +150,13 @@ class VCenterConnector(NetControllerConnector):
break break
return obj return obj
class VCenterJob(NetControllerJob):
connector = VCenterConnector
def __init__(self):
self._properties = {
"vlan": [0, "get_vlans_list"],
}

View File

@ -1,4 +1,5 @@
import os import os
import sys
from flask import Flask, request, abort, send_from_directory from flask import Flask, request, abort, send_from_directory
from flask.ext import restful from flask.ext import restful
from flask.ext.pymongo import PyMongo from flask.ext.pymongo import PyMongo
@ -7,7 +8,8 @@ import bson.json_util
import json import json
from datetime import datetime from datetime import datetime
import dateutil.parser import dateutil.parser
from connectors.vcenter import VCenterConnector from connectors.vcenter import VCenterJob, VCenterConnector
from connectors.demo import DemoJob, DemoConnector
MONGO_URL = os.environ.get('MONGO_URL') MONGO_URL = os.environ.get('MONGO_URL')
if not MONGO_URL: if not MONGO_URL:
@ -17,6 +19,10 @@ app = Flask(__name__)
app.config['MONGO_URI'] = MONGO_URL app.config['MONGO_URI'] = MONGO_URL
mongo = PyMongo(app) mongo = PyMongo(app)
available_jobs = [VCenterJob, DemoJob]
active_connectors = {}
class Root(restful.Resource): class Root(restful.Resource):
def get(self): def get(self):
return { return {
@ -64,9 +70,9 @@ class Job(restful.Resource):
class Connector(restful.Resource): class Connector(restful.Resource):
def get(self, **kw): def get(self, **kw):
type = request.args.get('type') type = request.args.get('type')
if (type == 'vcenter'): if (type == 'VCenterConnector'):
vcenter = VCenterConnector() vcenter = VCenterConnector()
properties = mongo.db.connector.find_one({"type": 'vcenter'}) properties = mongo.db.connector.find_one({"type": 'VCenterConnector'})
if properties: if properties:
vcenter.load_properties(properties) vcenter.load_properties(properties)
ret = vcenter.get_properties() ret = vcenter.get_properties()
@ -76,17 +82,61 @@ class Connector(restful.Resource):
def post(self, **kw): def post(self, **kw):
settings_json = json.loads(request.data) settings_json = json.loads(request.data)
if (settings_json.get("type") == 'vcenter'): if (settings_json.get("type") == 'VCenterConnector'):
# preserve password # preserve password
properties = mongo.db.connector.find_one({"type": 'vcenter'}) properties = mongo.db.connector.find_one({"type": 'VCenterConnector'})
if properties and (not settings_json.has_key("password") or not settings_json["password"]): if properties and (not settings_json.has_key("password") or not settings_json["password"]):
settings_json["password"] = properties.get("password") settings_json["password"] = properties.get("password")
return mongo.db.connector.update({"type": 'vcenter'}, return mongo.db.connector.update({"type": 'VCenterConnector'},
{"$set": settings_json}, {"$set": settings_json},
upsert=True) upsert=True)
class JobCreation(restful.Resource):
def get(self, **kw):
jobtype = request.args.get('type')
if not jobtype:
res = []
update_connectors()
for con in available_jobs:
if con.connector.__name__ in active_connectors:
res.append({"title": con.__name__, "$ref": "/jobcreate?type=" + con.__name__})
return {"oneOf": res}
job = None
for jobclass in available_jobs:
if jobclass.__name__ == jobtype:
job = jobclass()
if job and job.connector.__name__ in active_connectors.keys():
properties = dict()
job_prop = job.get_job_properties()
for prop in job_prop:
properties[prop] = dict({})
if type(job_prop[prop][0]) is int:
properties[prop]["type"] = "number"
elif type(job_prop[prop][0]) is bool:
properties[prop]["type"] = "boolean"
else:
properties[prop]["type"] = "string"
if job_prop[prop][1]:
properties[prop]["enum"] = list(active_connectors[job.connector.__name__].__getattribute__(job_prop[prop][1])())
res = dict({
"title": "%s Job" % jobtype,
"type": "object",
"options": {
"disable_collapse": True,
"disable_properties": True,
},
"properties": properties
})
return res
return {}
def normalize_obj(obj): def normalize_obj(obj):
if obj.has_key('_id') and not obj.has_key('id'): if obj.has_key('_id') and not obj.has_key('id'):
@ -113,6 +163,30 @@ def output_json(obj, code, headers=None):
resp.headers.extend(headers or {}) resp.headers.extend(headers or {})
return resp return resp
def refresh_connector_config(name):
properties = mongo.db.connector.find_one({"type": name})
if properties:
active_connectors[name].load_properties(properties)
def update_connectors():
for con in available_jobs:
connector_name = con.connector.__name__
if connector_name not in active_connectors:
active_connectors[connector_name] = con.connector()
if not active_connectors[connector_name].is_connected():
refresh_connector_config(connector_name)
try:
app.logger.info("Trying to activate connector: %s" % connector_name)
active_connectors[connector_name].connect()
except Exception, e:
active_connectors.pop(connector_name)
app.logger.info("Error activating connector: %s, reason: %s" % (connector_name, e))
@app.route('/admin/<path:path>') @app.route('/admin/<path:path>')
def send_admin(path): def send_admin(path):
return send_from_directory('admin/ui', path) return send_from_directory('admin/ui', path)
@ -124,6 +198,7 @@ api.representations = DEFAULT_REPRESENTATIONS
api.add_resource(Root, '/api') api.add_resource(Root, '/api')
api.add_resource(Job, '/job') api.add_resource(Job, '/job')
api.add_resource(Connector, '/connector') api.add_resource(Connector, '/connector')
api.add_resource(JobCreation, '/jobcreate')
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key')) app.run(host='0.0.0.0', debug=True, ssl_context=('server.crt', 'server.key'))