diff --git a/dashboard/api/windc.py b/dashboard/api/windc.py
index 15aa289..a58d617 100644
--- a/dashboard/api/windc.py
+++ b/dashboard/api/windc.py
@@ -35,27 +35,35 @@ def windcclient(request):
% (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):
return windcclient(request).services.create(datacenter, parameters)
+
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)
diff --git a/dashboard/windc/forms.py b/dashboard/windc/forms.py
index 7c1329f..80ba327 100644
--- a/dashboard/windc/forms.py
+++ b/dashboard/windc/forms.py
@@ -20,19 +20,30 @@
import logging
+from django import forms
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 exceptions
from horizon import messages
+import pdb
LOG = logging.getLogger(__name__)
+class WizardFormServiceType(forms.Form):
+ _type = forms.ChoiceField(label=_("Service Type"))
+
+
+class WizardFormConfiguration(forms.Form):
+ subject = forms.CharField(max_length=100)
+ sender = forms.CharField(max_length=1)
+
+
class UpdateWinDC(forms.SelfHandlingForm):
tenant_id = forms.CharField(widget=forms.HiddenInput)
data_center = forms.CharField(widget=forms.HiddenInput)
diff --git a/dashboard/windc/tables.py b/dashboard/windc/tables.py
index bf34d4b..85a1c8f 100644
--- a/dashboard/windc/tables.py
+++ b/dashboard/windc/tables.py
@@ -81,8 +81,7 @@ class DeleteDataCenter(tables.BatchAction):
return True
def action(self, request, datacenter_id):
- datacenter = api.windc.datacenters_get(request, datacenter_id)
- api.windc.datacenters_delete(request, datacenter)
+ api.windc.datacenters_delete(request, datacenter_id)
class DeleteService(tables.BatchAction):
@@ -101,9 +100,8 @@ class DeleteService(tables.BatchAction):
link = request.__dict__['META']['HTTP_REFERER']
datacenter_id = re.search('windc/(\S+)', link).group(0)[6:-1]
##############
- datacenter = api.windc.datacenters_get(request, datacenter_id)
-
- api.windc.services_delete(request, datacenter, service_id)
+
+ api.windc.services_delete(request, datacenter_id, service_id)
class EditService(tables.LinkAction):
@@ -116,6 +114,16 @@ class EditService(tables.LinkAction):
return True
+class Wizard(tables.LinkAction):
+ name = "wizard"
+ verbose_name = _("Wizard")
+ 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")
@@ -144,18 +152,36 @@ class WinDCTable(tables.DataTable):
name = "windc"
verbose_name = _("Windows Data Centers")
row_class = UpdateRow
- table_actions = (CreateDataCenter,)
- row_actions = (ShowDataCenterServices,DeleteDataCenter)
+ table_actions = (CreateDataCenter, Wizard)
+ row_actions = (ShowDataCenterServices, DeleteDataCenter)
+
+
+STATUS_DISPLAY_CHOICES = (
+ ("create", "Deploy"),
+)
class WinServicesTable(tables.DataTable):
- name = tables.Column('dc_name', verbose_name=_('Name'))
+
+ STATUS_CHOICES = (
+ (None, True),
+ ("deployed", True),
+ ("active", True),
+ ("error", False),
+ )
+
+ name = tables.Column('dc_name', verbose_name=_('Name'),
+ link=("horizon:project:windc:service_details"),)
_type = tables.Column('type', verbose_name=_('Type'))
- status = tables.Column('status', verbose_name=_('Status'))
+ status = tables.Column('status', verbose_name=_('Status'),
+ status=True,
+ status_choices=STATUS_CHOICES,
+ display_choices=STATUS_DISPLAY_CHOICES)
class Meta:
name = "services"
verbose_name = _("Services")
row_class = UpdateRow
+ status_columns = ['status']
table_actions = (CreateService,)
row_actions = (EditService, DeleteService)
diff --git a/dashboard/windc/templates/windc/_dc_help.html b/dashboard/windc/templates/windc/_dc_help.html
index 1cb4efc..5e29e2d 100644
--- a/dashboard/windc/templates/windc/_dc_help.html
+++ b/dashboard/windc/templates/windc/_dc_help.html
@@ -1,2 +1,3 @@
{% load i18n %}
-
{% blocktrans %}You can deploy few domain controllers with one name.{% endblocktrans %}
\ No newline at end of file
+{% blocktrans %}You can deploy few Active Directory services with one domain name.{% endblocktrans %}
+{% blocktrans %}The DNS service will automatically created for each Active Directory.{% endblocktrans %}
\ No newline at end of file
diff --git a/dashboard/windc/templates/windc/_services_tabs.html b/dashboard/windc/templates/windc/_services_tabs.html
new file mode 100644
index 0000000..9839711
--- /dev/null
+++ b/dashboard/windc/templates/windc/_services_tabs.html
@@ -0,0 +1,28 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% block modal-header %}{% trans "Create Service" %}{% endblock %}
+
+{% block modal-body %}
+Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}
+
+{% endblock %}
\ No newline at end of file
diff --git a/dashboard/windc/urls.py b/dashboard/windc/urls.py
index 6654ed2..faa54d9 100644
--- a/dashboard/windc/urls.py
+++ b/dashboard/windc/urls.py
@@ -20,8 +20,10 @@
from django.conf.urls.defaults import patterns, url
-from .views import IndexView, CreateWinDCView, WinServices, CreateWinServiceView
-
+from .views import IndexView, WinServices, \
+ CreateWinDCView, CreateWinServiceView
+from .views import Wizard
+from .forms import WizardFormServiceType, WizardFormConfiguration
VIEW_MOD = 'openstack_dashboard.dashboards.project.windc.views'
@@ -29,6 +31,11 @@ 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[^/]+)/$', WinServices.as_view(),
- name='services')
+ url(r'^(?P[^/]+)/$', WinServices.as_view(),
+ name='services'),
+ url(r'^update$',
+ Wizard.as_view([WizardFormServiceType, WizardFormConfiguration]),
+ name='update'),
+ url(r'^(?P[^/]+)/$', WinServices.as_view(),
+ name='service_details')
)
diff --git a/dashboard/windc/views.py b/dashboard/windc/views.py
index 9c34c29..87595b2 100644
--- a/dashboard/windc/views.py
+++ b/dashboard/windc/views.py
@@ -15,7 +15,8 @@
# 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.
+# License for the specific language governing permissions and limitations
+# under the License.
"""
Views for managing instances.
@@ -24,24 +25,42 @@ import logging
from django import http
from django import shortcuts
+from django.views import generic
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.datastructures import SortedDict
from django.utils.translation import ugettext_lazy as _
+from django.contrib.formtools.wizard.views import SessionWizardView
from horizon import exceptions
from horizon import forms
from horizon import tabs
from horizon import tables
from horizon import workflows
+from horizon.forms.views import ModalFormMixin
from openstack_dashboard import api
from .tables import WinDCTable, WinServicesTable
from .workflows import CreateWinService, CreateWinDC
+from .forms import WizardFormServiceType, WizardFormConfiguration
-
+import pdb
LOG = logging.getLogger(__name__)
+class Wizard(ModalFormMixin, SessionWizardView, generic.FormView):
+ template_name = 'project/windc/services_tabs.html'
+
+ def done(self, form_list, **kwargs):
+ #do_something_with_the_form_data(form_list)
+ return HttpResponseRedirect('/')
+
+ def get_form(self, step=None, data=None, files=None):
+ form = super(Wizard, self).get_form(step, data, files)
+ print step
+ print data
+ return form
+
+
class IndexView(tables.DataTableView):
table_class = WinDCTable
template_name = 'project/windc/index.html'
@@ -69,7 +88,7 @@ class WinServices(tables.DataTableView):
def get_data(self):
try:
- dc_id = self.kwargs['domain_controller_id']
+ dc_id = self.kwargs['data_center_id']
datacenter = api.windc.datacenters_get(self.request, dc_id)
self.dc_name = datacenter.name
services = api.windc.services_list(self.request, datacenter)
@@ -91,6 +110,7 @@ class CreateWinDCView(workflows.WorkflowView):
initial['user_id'] = self.request.user.id
return initial
+
class CreateWinServiceView(workflows.WorkflowView):
workflow_class = CreateWinService
template_name = "project/windc/create.html"
diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py
index 4eb32e9..dcd9b18 100644
--- a/dashboard/windc/workflows.py
+++ b/dashboard/windc/workflows.py
@@ -84,11 +84,7 @@ 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"),
+ dc_count = forms.IntegerField(label=_("Instances Count"),
required=True,
min_value=1,
max_value=100,
@@ -108,7 +104,7 @@ class ConfigureWinDCAction(workflows.Action):
"Recovery Mode."))
class Meta:
- name = _("Domain Controllers")
+ name = _("Active Directory")
help_text_template = ("project/windc/_dc_help.html")
@@ -169,7 +165,7 @@ class CreateWinService(workflows.Workflow):
default_steps = (SelectProjectUser,
ConfigureWinDC,
ConfigureWinIIS)
-
+
def format_status_message(self, message):
dc_name = self.context.get('dc_name', 'noname')
return message % dc_name
@@ -192,7 +188,6 @@ class CreateWinService(workflows.Workflow):
return False
-
class CreateWinDC(workflows.Workflow):
slug = "create"
name = _("Create Windows Data Center")
diff --git a/dashboard/windcclient/v1/datacenters.py b/dashboard/windcclient/v1/datacenters.py
index 636021d..3d184f1 100644
--- a/dashboard/windcclient/v1/datacenters.py
+++ b/dashboard/windcclient/v1/datacenters.py
@@ -35,9 +35,9 @@ class DCManager(base.Manager):
body.update(extra)
return self._create('/datacenters', body, 'datacenter')
- def delete(self, datacenter):
- return self._delete("/datacenters/%s" % base.getid(datacenter))
+ def delete(self, datacenter_id):
+ return self._delete("/datacenters/%s" % datacenter_id)
- def get(self, datacenter):
- return self._get("/datacenters/%s" % base.getid(datacenter),
+ def get(self, datacenter_id):
+ return self._get("/datacenters/%s" % datacenter_id,
'datacenter')
diff --git a/dashboard/windcclient/v1/services.py b/dashboard/windcclient/v1/services.py
index 8309338..809a30c 100644
--- a/dashboard/windcclient/v1/services.py
+++ b/dashboard/windcclient/v1/services.py
@@ -37,10 +37,9 @@ class DCServiceManager(base.Manager):
return self._create("/datacenters/%s/services" % base.getid(datacenter),
body, 'service')
- def delete(self, datacenter, service):
+ def delete(self, datacenter_id, service_id):
return self._delete("/datacenters/%s/services/%s" % \
- (base.getid(datacenter),
- base.getid(service)))
+ (datacenter_id, service_id))
def get(self, datacenter, service):
return self._get("/datacenters/%s/services/%s" % \
diff --git a/tests/deploy.sh b/tests/deploy.sh
new file mode 100644
index 0000000..e57207d
--- /dev/null
+++ b/tests/deploy.sh
@@ -0,0 +1,82 @@
+#!/usr/bin/expect -d
+# The following directories should be created for this script:
+# /opt/stack/devstack
+# /opt/stack/keero
+# the ssh key should be in directory /opt/stack/.ssh/
+# the iso file with windows should be in directory /opt/stack/
+
+set timeout 1200
+
+send_user "\n\nStart to login to the test bed...\n\n"
+
+spawn /usr/bin/ssh [lindex $argv 0]@[lindex $argv 1]
+expect "password"
+send -- "EVYiMCVZX9\n"
+expect "*#*"
+
+send -- "su - stack\n"
+expect "*$*"
+
+send -- "sudo killall python\n"
+expect "*$*"
+send -- "cd ~/devstack\n"
+expect "*$*"
+send -- "./unstack.sh\n"
+expect "*$*"
+send -- "./stack.sh\n"
+expect "*Would you like to start it now?*"
+send -- "y\n"
+expect "*stack.sh completed*"
+
+send -- "source openrc admin admin\n"
+expect "*$*"
+
+send -- "cd ~\n"
+expect "*$*"
+
+send -- "nova keypair-add keero-linux-keys > heat_key.priv\n"
+expect "*$*"
+
+send -- "glance image-create --name 'ws-2012-full-agent' --is-public true --container-format ovf --disk-format qcow2 < ws-2012-full-agent.qcow2\n"
+expect "*$*"
+
+send -- "cd ~/keero\n"
+expect "*$*"
+send -- "git pull\n"
+expect "/.ssh/id_rsa"
+send -- "swordfish\n"
+expect "*$*"
+send -- "cp -Rf ~/keero/dashboard/windc /opt/stack/horizon/openstack_dashboard/dashboards/project\n"
+expect "*$*"
+send -- "cp -f ~/keero/dashboard/api/windc.py /opt/stack/horizon/openstack_dashboard/api/\n"
+expect "*$*"
+send -- "cp -Rf ~/keero/dashboard/windcclient /opt/stack/horizon/\n"
+expect "*$*"
+send -- "cd ~/keero/windc\n"
+expect "*$*"
+send -- "rm -rf windc.sqlite\n"
+expect "*$*"
+send -- "./tools/with_venv.sh ./bin/windc-api --config-file=./etc/windc-api-paste.ini --dbsync\n"
+expect "*$*"
+send -- "logout\n"
+expect "*#*"
+
+send -- "rabbitmq-plugins enable rabbitmq_management\n"
+expect "*#*"
+send -- "service rabbitmq-server restart\n"
+expect "*#*"
+send -- "rabbitmqctl add_user keero keero\n"
+expect "*#*"
+send -- "rabbitmqctl set_user_tags keero administrator\n"
+expect "*#*"
+
+send -- "su - stack\n"
+expect "*$*"
+send -- "cd /opt/stack/devstack\n"
+expect "*$*"
+send -- "source openrc admin admin\n"
+expect "*$*"
+send -- "cd /opt/stack/keero/windc\n"
+expect "*$*"
+send -- "sudo ./tools/with_venv.sh ./bin/windc-api --config-file=./etc/windc-api-paste.ini > /opt/stack/tests_windc_daemon.log &\n"
+expect "*$*"
diff --git a/tests/selenium/conf.ini b/tests/selenium/conf.ini
new file mode 100644
index 0000000..8b43288
--- /dev/null
+++ b/tests/selenium/conf.ini
@@ -0,0 +1,4 @@
+[server]
+address=http://172.18.124.101
+user=admin
+password=AkvareL707
\ No newline at end of file
diff --git a/tests/selenium/datacenters_page.py b/tests/selenium/datacenters_page.py
new file mode 100644
index 0000000..b354efa
--- /dev/null
+++ b/tests/selenium/datacenters_page.py
@@ -0,0 +1,55 @@
+import re
+from login_page import LoginPage
+
+
+class DataCentersPage():
+ page = None
+
+ def __init__(self):
+ start_page = LoginPage()
+ self.page = start_page.login()
+ self.page.find_element_by_link_text('Project').click()
+ self.page.find_element_by_link_text('Windows Data Centers').click()
+
+ def create_data_center(self, name):
+ button_text = 'Create Windows Data Center'
+ self.page.find_element_by_link_text(button_text).click()
+
+ name_field = self.page.find_element_by_id('id_name')
+ xpath = "//input[@value='Create']"
+ button = self.page.find_element_by_xpath(xpath)
+
+ name_field.clear()
+ name_field.send_keys(name)
+
+ button.click()
+
+ return self.page
+
+ def find_data_center(self, name):
+ return self.page.find_element_by_link_text(name)
+
+ def delete_data_center(self, name):
+ datacenter = self.find_data_center(name)
+ link = datacenter.get_attribute('href')
+ datacenter_id = re.search('windc/(\S+)', link).group(0)[6:-1]
+
+ xpath = ".//*[@id='windc__row__%s']/td[3]/div/a[2]" % datacenter_id
+ more_button = self.page.find_element_by_xpath(xpath)
+
+ more_button.click()
+
+ delete_button_id = "windc__row_%s__action_delete" % datacenter_id
+ delete_button = self.page.find_element_by_id(delete_button_id)
+
+ delete_button.click()
+
+ self.page.find_element_by_link_text("Delete Data Center").click()
+
+ return self.page
+
+ def select_data_center(self, name):
+ datacenter = self.page.find_data_center(name)
+ datacenter.click()
+
+ return self.page
diff --git a/tests/selenium/login_page.py b/tests/selenium/login_page.py
new file mode 100644
index 0000000..f0f357c
--- /dev/null
+++ b/tests/selenium/login_page.py
@@ -0,0 +1,30 @@
+import ConfigParser
+from selenium import webdriver
+
+
+class LoginPage():
+
+ def login(self):
+ config = ConfigParser.RawConfigParser()
+ config.read('conf.ini')
+ url = config.get('server', 'address')
+ user = config.get('server', 'user')
+ password = config.get('server', 'password')
+
+ page = webdriver.Firefox()
+ page.set_page_load_timeout(30)
+ page.implicitly_wait(30)
+ page.get(url)
+ name = page.find_element_by_name('username')
+ pwd = page.find_element_by_name('password')
+ xpath = "//button[@type='submit']"
+ button = page.find_element_by_xpath(xpath)
+
+ name.clear()
+ name.send_keys(user)
+ pwd.clear()
+ pwd.send_keys(password)
+
+ button.click()
+
+ return page
diff --git a/tests/selenium/services_page.py b/tests/selenium/services_page.py
new file mode 100644
index 0000000..3571df0
--- /dev/null
+++ b/tests/selenium/services_page.py
@@ -0,0 +1,55 @@
+import ConfigParser
+from selenium import webdriver
+
+
+class ServicesPage():
+ page = None
+
+ def __init__(self, page):
+ self.page = page
+
+ def create_service(self, service_type, parameters):
+
+ button_id = 'services__action_CreateService'
+ button = self.page.find_element_by_id(button_id)
+ button.click()
+
+ self.select_type_of_service(service_type)
+
+ for parameter in parameters:
+ field = self.page.find_element_by_name(parameter.key)
+ field.clear()
+ field.send_keys(parameter.value)
+
+ xpath = "//input[@value='Deploy']"
+ deploy_button = self.page.find_element_by_xpath(xpath)
+ deploy_button.click()
+
+ return page
+
+ def select_type_of_service(self, service_type):
+ tab = find_element_by_link_text(service_type)
+ tab.click()
+ return self.page
+
+ def find_service(self, name):
+ return self.page.find_element_by_link_text(name)
+
+ def delete_service(self, name):
+ service = self.find_data_center(name)
+ link = service.get_attribute('href')
+
+ service_id = re.search('windc/(\S+)', link).group(0)[6:-1]
+
+ xpath = ".//*[@id='services__row__%s']/td[5]/div/a[2]" % service_id
+ more_button = self.page.find_element_by_xpath(xpath)
+ more_button.click()
+
+ delete_button_id = "services__row_%s__action_delete" % datacenter_id
+ delete_button = self.page.find_element_by_id(delete_button_id)
+
+ delete_button.click()
+
+ self.page.find_element_by_link_text("Delete Service").click()
+
+ return self.page
diff --git a/tests/selenium/test.py b/tests/selenium/test.py
new file mode 100644
index 0000000..b95af41
--- /dev/null
+++ b/tests/selenium/test.py
@@ -0,0 +1,35 @@
+import untitests
+from datacenters_page import DataCentersPage
+
+
+class SanityTests():
+
+ def setUp(self):
+ self.page = DataCentersPage()
+
+ def tearDown(self):
+ self.page.close()
+
+ def test_01_create_data_center(self):
+ self.page.create_data_center('dc1')
+ assert self.page.find_data_center('dc1') is not None
+
+ def test_02_delete_data_center(self):
+ page.delete_data_center('dc1')
+ assert self.page.find_data_center('dc1') is None
+
+ def test_03_create_data_centers(self):
+ for i in range(1, 20):
+ name = 'datacenter' + str(i)
+ self.page.create_data_center(name)
+ assert self.page.find_data_center(name) is not None
+
+ def test_04_delete_data_centers(self):
+ page.delete_data_center('datacenter1')
+ page.delete_data_center('datacenter20')
+ assert self.page.find_data_center('datacenter1') is None
+ assert self.page.find_data_center('datacenter20') is None
+
+ for i in range(2, 19):
+ name = 'datacenter' + str(i)
+ assert self.page.find_data_center(name) is not None