().FirstOrDefault();
+ if(descriptionAttribute != null)
+ {
+ serviceInstaller.Description = descriptionAttribute.Description;
+ }
+
+ Installers.Add(serviceInstaller);
+ }
+
+ Installers.Add(processInstaller);
+
+ }
+ }
+}
diff --git a/WindowsAgent/WindowsAgent/packages.config b/WindowsAgent/WindowsAgent/packages.config
new file mode 100644
index 0000000..7aabef8
--- /dev/null
+++ b/WindowsAgent/WindowsAgent/packages.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/WindowsAgent/packages/repositories.config b/WindowsAgent/packages/repositories.config
new file mode 100644
index 0000000..7753eee
--- /dev/null
+++ b/WindowsAgent/packages/repositories.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/dashboard/ReadMe.txt b/dashboard/ReadMe.txt
new file mode 100644
index 0000000..45d191b
--- /dev/null
+++ b/dashboard/ReadMe.txt
@@ -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
\ No newline at end of file
diff --git a/dashboard/api/windc.py b/dashboard/api/windc.py
new file mode 100644
index 0000000..e0407eb
--- /dev/null
+++ b/dashboard/api/windc.py
@@ -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)
diff --git a/dashboard/windc/__init__.py b/dashboard/windc/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/dashboard/windc/forms.py b/dashboard/windc/forms.py
new file mode 100644
index 0000000..7c1329f
--- /dev/null
+++ b/dashboard/windc/forms.py
@@ -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)
diff --git a/dashboard/windc/panel.py b/dashboard/windc/panel.py
new file mode 100644
index 0000000..0731194
--- /dev/null
+++ b/dashboard/windc/panel.py
@@ -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)
diff --git a/dashboard/windc/tables.py b/dashboard/windc/tables.py
new file mode 100644
index 0000000..8b24622
--- /dev/null
+++ b/dashboard/windc/tables.py
@@ -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,)
diff --git a/dashboard/windc/tabs.py b/dashboard/windc/tabs.py
new file mode 100644
index 0000000..95b7217
--- /dev/null
+++ b/dashboard/windc/tabs.py
@@ -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
diff --git a/dashboard/windc/templates/windc/_data_center_help.html b/dashboard/windc/templates/windc/_data_center_help.html
new file mode 100644
index 0000000..68ffe5a
--- /dev/null
+++ b/dashboard/windc/templates/windc/_data_center_help.html
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}Data Center is an instance with different services.{% endblocktrans %}
\ No newline at end of file
diff --git a/dashboard/windc/templates/windc/_dc_help.html b/dashboard/windc/templates/windc/_dc_help.html
new file mode 100644
index 0000000..1cb4efc
--- /dev/null
+++ b/dashboard/windc/templates/windc/_dc_help.html
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}You can deploy few domain controllers with one name.{% endblocktrans %}
\ No newline at end of file
diff --git a/dashboard/windc/templates/windc/_iis_help.html b/dashboard/windc/templates/windc/_iis_help.html
new file mode 100644
index 0000000..e8004c8
--- /dev/null
+++ b/dashboard/windc/templates/windc/_iis_help.html
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans %}You can deploy few Internet Information Services in one domain.{% endblocktrans %}
\ No newline at end of file
diff --git a/dashboard/windc/templates/windc/_services.html b/dashboard/windc/templates/windc/_services.html
new file mode 100644
index 0000000..1869508
--- /dev/null
+++ b/dashboard/windc/templates/windc/_services.html
@@ -0,0 +1,3 @@
+{% load i18n sizeformat %}
+
+{% trans "Services" %}
\ No newline at end of file
diff --git a/dashboard/windc/templates/windc/create.html b/dashboard/windc/templates/windc/create.html
new file mode 100644
index 0000000..0508b68
--- /dev/null
+++ b/dashboard/windc/templates/windc/create.html
@@ -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 %}
diff --git a/dashboard/windc/templates/windc/create_dc.html b/dashboard/windc/templates/windc/create_dc.html
new file mode 100644
index 0000000..2fc5894
--- /dev/null
+++ b/dashboard/windc/templates/windc/create_dc.html
@@ -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 %}
diff --git a/dashboard/windc/templates/windc/index.html b/dashboard/windc/templates/windc/index.html
new file mode 100644
index 0000000..fa172ab
--- /dev/null
+++ b/dashboard/windc/templates/windc/index.html
@@ -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 %}
diff --git a/dashboard/windc/templates/windc/services.html b/dashboard/windc/templates/windc/services.html
new file mode 100644
index 0000000..2c7af81
--- /dev/null
+++ b/dashboard/windc/templates/windc/services.html
@@ -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 %}
diff --git a/dashboard/windc/templates/windc/services_tabs.html b/dashboard/windc/templates/windc/services_tabs.html
new file mode 100644
index 0000000..8630ff8
--- /dev/null
+++ b/dashboard/windc/templates/windc/services_tabs.html
@@ -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 %}
+
+
+ {{ tab_group.render }}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/dashboard/windc/templates/windc/update.html b/dashboard/windc/templates/windc/update.html
new file mode 100644
index 0000000..aba3dc9
--- /dev/null
+++ b/dashboard/windc/templates/windc/update.html
@@ -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 %}
diff --git a/dashboard/windc/urls.py b/dashboard/windc/urls.py
new file mode 100644
index 0000000..6654ed2
--- /dev/null
+++ b/dashboard/windc/urls.py
@@ -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[^/]+)/$', WinServices.as_view(),
+ name='services')
+)
diff --git a/dashboard/windc/views.py b/dashboard/windc/views.py
new file mode 100644
index 0000000..9c34c29
--- /dev/null
+++ b/dashboard/windc/views.py
@@ -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
diff --git a/dashboard/windc/workflows.py b/dashboard/windc/workflows.py
new file mode 100644
index 0000000..9724aa8
--- /dev/null
+++ b/dashboard/windc/workflows.py
@@ -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
diff --git a/dashboard/windcclient/__init__.py b/dashboard/windcclient/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/dashboard/windcclient/common/__init__.py b/dashboard/windcclient/common/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/dashboard/windcclient/common/base.py b/dashboard/windcclient/common/base.py
new file mode 100644
index 0000000..9f03504
--- /dev/null
+++ b/dashboard/windcclient/common/base.py
@@ -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
diff --git a/dashboard/windcclient/common/client.py b/dashboard/windcclient/common/client.py
new file mode 100644
index 0000000..d8c9eb7
--- /dev/null
+++ b/dashboard/windcclient/common/client.py
@@ -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)
diff --git a/dashboard/windcclient/common/exceptions.py b/dashboard/windcclient/common/exceptions.py
new file mode 100644
index 0000000..4d17b8d
--- /dev/null
+++ b/dashboard/windcclient/common/exceptions.py
@@ -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)
diff --git a/dashboard/windcclient/common/service_catalog.py b/dashboard/windcclient/common/service_catalog.py
new file mode 100644
index 0000000..d2a91d6
--- /dev/null
+++ b/dashboard/windcclient/common/service_catalog.py
@@ -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.')
diff --git a/dashboard/windcclient/common/utils.py b/dashboard/windcclient/common/utils.py
new file mode 100644
index 0000000..cabcba8
--- /dev/null
+++ b/dashboard/windcclient/common/utils.py
@@ -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)
diff --git a/dashboard/windcclient/shell.py b/dashboard/windcclient/shell.py
new file mode 100644
index 0000000..196c7a7
--- /dev/null
+++ b/dashboard/windcclient/shell.py
@@ -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='',
+ default=utils.env('OS_USERNAME'),
+ help='Defaults to env[OS_USERNAME]')
+
+ parser.add_argument('--os_password',
+ metavar='',
+ default=utils.env('OS_PASSWORD'),
+ help='Defaults to env[OS_PASSWORD]')
+
+ parser.add_argument('--os_tenant_name',
+ metavar='',
+ default=utils.env('OS_TENANT_NAME'),
+ help='Defaults to env[OS_TENANT_NAME]')
+
+ parser.add_argument('--os_tenant_id',
+ metavar='',
+ default=utils.env('OS_TENANT_ID'),
+ help='Defaults to env[OS_TENANT_ID]')
+
+ parser.add_argument('--os_auth_url',
+ metavar='',
+ default=utils.env('OS_AUTH_URL'),
+ help='Defaults to env[OS_AUTH_URL]')
+
+ parser.add_argument('--os_region_name',
+ metavar='',
+ default=utils.env('OS_REGION_NAME'),
+ help='Defaults to env[OS_REGION_NAME]')
+
+ parser.add_argument('--os_balancer_api_version',
+ metavar='',
+ 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='',
+ default=utils.env('SERVICE_TOKEN'),
+ help='Defaults to env[SERVICE_TOKEN]')
+
+ parser.add_argument('--endpoint',
+ metavar='',
+ 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='')
+
+ 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='', nargs='?',
+ help='Display help for ')
+ 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
diff --git a/dashboard/windcclient/v1/__init__.py b/dashboard/windcclient/v1/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/dashboard/windcclient/v1/client.py b/dashboard/windcclient/v1/client.py
new file mode 100644
index 0000000..3928773
--- /dev/null
+++ b/dashboard/windcclient/v1/client.py
@@ -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)
diff --git a/dashboard/windcclient/v1/datacenters.py b/dashboard/windcclient/v1/datacenters.py
new file mode 100644
index 0000000..93aaef2
--- /dev/null
+++ b/dashboard/windcclient/v1/datacenters.py
@@ -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 "" % 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')
diff --git a/dashboard/windcclient/v1/services.py b/dashboard/windcclient/v1/services.py
new file mode 100644
index 0000000..3216f3c
--- /dev/null
+++ b/dashboard/windcclient/v1/services.py
@@ -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 "" % 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')
diff --git a/windc/heat_run b/windc/heat_run
new file mode 100755
index 0000000..69e4182
--- /dev/null
+++ b/windc/heat_run
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+source openrc.sh
+heat "$@"
+
diff --git a/windc/tests/manual/createServiceParameters b/windc/tests/manual/createServiceParameters
index 6e9817c..0954f92 100644
--- a/windc/tests/manual/createServiceParameters
+++ b/windc/tests/manual/createServiceParameters
@@ -4,5 +4,5 @@
"domain": "ACME.cloud",
"AdminUser": "Admin",
"AdminPassword": "StrongPassword",
-"DomainControllerNames": ["APP-AD001","APP-AD002"]
+"DomainControllerNames": ["AD-DC001"]
}
diff --git a/windc/tools/pip-requires b/windc/tools/pip-requires
index 0cb916b..d860a56 100644
--- a/windc/tools/pip-requires
+++ b/windc/tools/pip-requires
@@ -3,7 +3,7 @@
# package to get the right headers...
greenlet>=0.3.1
-SQLAlchemy>=0.7
+SQLAlchemy<=0.7.9
anyjson
eventlet>=0.9.12
PasteDeploy
@@ -15,7 +15,7 @@ sqlalchemy-migrate>=0.7.2
httplib2
kombu
iso8601>=0.1.4
-
+PyChef
# For paste.util.template used in keystone.common.template
Paste
diff --git a/windc/windc/adapters/openstack.py b/windc/windc/adapters/openstack.py
new file mode 100644
index 0000000..9ca6733
--- /dev/null
+++ b/windc/windc/adapters/openstack.py
@@ -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
+
diff --git a/windc/windc/api/v1/datacenters.py b/windc/windc/api/v1/datacenters.py
index 2b0e1f9..9e05062 100644
--- a/windc/windc/api/v1/datacenters.py
+++ b/windc/windc/api/v1/datacenters.py
@@ -26,24 +26,17 @@ from windc.db import api as db_api
LOG = logging.getLogger(__name__)
-class Controller(object):
+class Datacenters_Controller(object):
def __init__(self, conf):
LOG.debug("Creating data centers controller with config:"
"datacenters.py %s", conf)
self.conf = conf
- @utils.verify_tenant
- def findLBforVM(self, req, tenant_id, vm_id):
- LOG.debug("Got index request. Request: %s", req)
- result = core_api.lb_find_for_vm(self.conf, tenant_id, vm_id)
- return {'loadbalancers': result}
-
@utils.verify_tenant
def index(self, req, tenant_id):
LOG.debug("Got index request. Request: %s", req)
result = core_api.dc_get_index(self.conf, tenant_id)
LOG.debug("Got list of datacenters: %s", result)
- result
return {'datacenters': result}
@utils.http_success_code(202)
@@ -80,4 +73,4 @@ def create_resource(conf):
"""Datacenters resource factory method"""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
- return wsgi.Resource(Controller(conf), deserializer, serializer)
\ No newline at end of file
+ return wsgi.Resource(Datacenters_Controller(conf), deserializer, serializer)
diff --git a/windc/windc/api/v1/router.py b/windc/windc/api/v1/router.py
index ed48ef1..79acc71 100644
--- a/windc/windc/api/v1/router.py
+++ b/windc/windc/api/v1/router.py
@@ -20,10 +20,6 @@ import routes
from windc.api.v1 import datacenters
from windc.api.v1 import services
-
-#from . import tasks
-
-
from openstack.common import wsgi
@@ -32,7 +28,7 @@ LOG = logging.getLogger(__name__)
class API(wsgi.Router):
- """WSGI router for balancer v1 API requests."""
+ """WSGI router for windc v1 API requests."""
def __init__(self, conf, **local_conf):
self.conf = conf
@@ -41,16 +37,20 @@ class API(wsgi.Router):
datacenter_resource = datacenters.create_resource(self.conf)
datacenter_collection = tenant_mapper.collection(
"datacenters", "datacenter",
- controller=datacenter_resource, member_prefix="/{datacenter_id}",
+ controller=datacenter_resource,
+ member_prefix="/{datacenter_id}",
formatted=False)
service_resource = services.create_resource(self.conf)
- service_collection = datacenter_collection.member.collection('services', 'service',
- controller=service_resource, member_prefix="/{service_id}",
- formatted=False)
- service_collection.member.connect("/{status}", action="changeServiceStatus",
- conditions={'method': ["PUT"]})
+ service_collection = datacenter_collection.member.\
+ collection('services','service',
+ controller=service_resource,
+ member_prefix="/{service_id}",
+ formatted=False)
+ service_collection.member.connect("/{status}",
+ action="changeServiceStatus",
+ conditions={'method': ["PUT"]})
mapper.connect("/servicetypes",
- controller=datacenter_resource,
- action="show_servicetypes",
- conditions={'method': ["GET"]})
+ controller=datacenter_resource,
+ action="show_servicetypes",
+ conditions={'method': ["GET"]})
super(API, self).__init__(mapper)
diff --git a/windc/windc/api/v1/services.py b/windc/windc/api/v1/services.py
index e635250..6040926 100644
--- a/windc/windc/api/v1/services.py
+++ b/windc/windc/api/v1/services.py
@@ -26,22 +26,17 @@ from windc.db import api as db_api
LOG = logging.getLogger(__name__)
-class Controller(object):
+class Services_Controller(object):
def __init__(self, conf):
LOG.debug("Creating services controller with config:"
"services.py %s", conf)
self.conf = conf
- @utils.verify_tenant
- def findLBforVM(self, req, tenant_id, vm_id):
- LOG.debug("Got index request. Request: %s", req)
- result = core_api.lb_find_for_vm(self.conf, tenant_id, vm_id)
- return {'loadbalancers': result}
-
@utils.verify_tenant
def index(self, req, tenant_id, datacenter_id):
LOG.debug("Got index request. Request: %s", req)
- result = core_api.service_get_index(self.conf, tenant_id, datacenter_id)
+ result = core_api.service_get_index(self.conf, tenant_id,
+ datacenter_id)
return {'services': result}
@utils.http_success_code(202)
@@ -61,19 +56,22 @@ class Controller(object):
@utils.verify_tenant
def delete(self, req, tenant_id, datacenter_id, service_id):
LOG.debug("Got delete request. Request: %s", req)
- core_api.delete_service(self.conf, tenant_id, datacenter_id, service_id)
+ core_api.delete_service(self.conf, tenant_id,
+ datacenter_id, service_id)
@utils.verify_tenant
def show(self, req, tenant_id, datacenter_id, service_id):
LOG.debug("Got loadbalancerr info request. Request: %s", req)
- result = core_api.service_get_data(self.conf, tenant_id, datacenter_id, service_id)
+ result = core_api.service_get_data(self.conf, tenant_id,
+ datacenter_id, service_id)
return {'service': result}
@utils.http_success_code(202)
@utils.verify_tenant
def update(self, req, tenant_id, datacenter_id, service_id, body):
LOG.debug("Got update request. Request: %s", req)
- core_api.update_service(self.conf, tenant_id, datacenter_id, service_id, body)
+ core_api.update_service(self.conf, tenant_id, datacenter_id,
+ service_id, body)
return {'service': {'id': service_id}}
@@ -81,7 +79,4 @@ def create_resource(conf):
"""Services resource factory method"""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
- return wsgi.Resource(Controller(conf), deserializer, serializer)
-
-
-
+ return wsgi.Resource(Services_Controller(conf), deserializer, serializer)
diff --git a/windc/windc/common/wsgi.py b/windc/windc/common/wsgi.py
index 8d01d31..3f1c6b5 100644
--- a/windc/windc/common/wsgi.py
+++ b/windc/windc/common/wsgi.py
@@ -46,7 +46,7 @@ from windc.common import utils
bind_opts = [
- cfg.StrOpt('bind_host', default='0.0.0.0'),
+ cfg.StrOpt('bind_host', default='localhost'),
cfg.IntOpt('bind_port'),
]
diff --git a/windc/windc/core/api.py b/windc/windc/core/api.py
index 5c12a2b..c9d1160 100644
--- a/windc/windc/core/api.py
+++ b/windc/windc/core/api.py
@@ -51,19 +51,22 @@ def update_dc(conf, tenant_id, datacenter_id, body):
old_dc = copy.deepcopy(dc)
db_api.pack_update(dc, body)
dc = db_api.datacenter_update(conf, datacenter_id, dc)
- event = events.Event(events.SCOPE_DATACENTER_CHANGE, events.ACTION_MODIFY)
+ event = events.Event(events.SCOPE_DATACENTER_CHANGE,
+ events.ACTION_MODIFY)
event.previous_state = old_dc
events.change_event(conf, event, dc)
pass
def service_get_index(conf, tenant_id, datacenter_id):
- srvcs = db_api.service_get_all_by_datacenter_id(conf, tenant_id, dtacenter_id)
+ srvcs = db_api.service_get_all_by_datacenter_id(conf, tenant_id,
+ datacenter_id)
srv_list = [db_api.unpack_extra(srv) for srv in srvcs]
return srv_list
pass
def create_service(conf, params):
- # We need to pack all attributes which are not defined by the model explicitly
+ # We need to pack all attributes which are not defined
+ # by the model explicitly
srv_params = db_api.service_pack_extra(params)
srv = db_api.service_create(conf, srv_params)
event = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_ADD)
@@ -80,7 +83,7 @@ def delete_service(conf, tenant_id, datacenter_id, service_id):
pass
def service_get_data(conf, tenant_id, datacenter_id, service_id):
- srv = db_api.service_get(conf,service_id, tenant_id)
+ srv = db_api.service_get(conf, service_id, tenant_id)
srv_data = db_api.unpack_extra(srv)
return srv_data
pass
@@ -93,4 +96,4 @@ def update_service(conf, tenant_id, datacenter_id, service_id, body):
event = events.Event(events.SCOPE_SERVICE_CHANGE, events.ACTION_MODIFY)
event.previous_state = old_srv
events.change_event(conf, event, srv)
- pass
\ No newline at end of file
+ pass
diff --git a/windc/windc/core/builder.py b/windc/windc/core/builder.py
index 2dc68e8..15a14e6 100644
--- a/windc/windc/core/builder.py
+++ b/windc/windc/core/builder.py
@@ -29,5 +29,9 @@ class Builder:
def build(self, context, event, data):
pass
+def create_context():
+ context = {}
+ context['commands']=[]
+ return context
diff --git a/windc/windc/core/builders/ActiveDirectory.py b/windc/windc/core/builders/ActiveDirectory.py
index 7181329..703703b 100644
--- a/windc/windc/core/builders/ActiveDirectory.py
+++ b/windc/windc/core/builders/ActiveDirectory.py
@@ -17,11 +17,14 @@
import logging
+import uuid
LOG = logging.getLogger(__name__)
from windc.core.builder import Builder
from windc.core import change_events as events
from windc.db import api as db_api
+from windc.core.templates import Template
+from windc.core import commands as command_api
class ActiveDirectory(Builder):
def __init__(self):
@@ -35,6 +38,8 @@ class ActiveDirectory(Builder):
LOG.info ("Got service change event. Analysing..")
if self.do_analysis(context, event, dc):
self.plan_changes(context, event, dc)
+
+ self.submit_commands(context, event, dc)
else:
LOG.debug("Not in my scope. Skip event.")
pass
@@ -44,10 +49,66 @@ class ActiveDirectory(Builder):
zones = data['zones']
if data['type'] == self.type and len(zones) == 1:
LOG.debug("It is a service which I should build.")
+ datacenter_id = data['datacenter_id']
+ dc = db_api.datacenter_get(context['conf'],data['tenant_id'],
+ data['datacenter_id'])
+ datacenter = db_api.unpack_extra(dc)
+ context['stack_name']=datacenter['name']
return True
else:
return False
def plan_changes(self, context, event, data):
+ # Here we can plan multiple command execution.
+ # It might be Heat call command, then chef call command and other
+ #
+ LOG.debug("Plan changes...")
+ self.prepare_template(context, event, data)
+ self.chef_configuration(context, event, data)
+ context['commands'].append(self.deploy_template_command(context, event, data))
+ context['commands'].append(self.chef_configuration_command(context, event, data))
pass
+ def prepare_template(self, context, event, data):
+ LOG.debug("Prepare CloudFormation Template...")
+ template = Template()
+ template.add_description('Base template for Active Directory deployment')
+ sec_grp = template.create_security_group('Security group for AD')
+ rule = template.create_securitygroup_rule('tcp','3389','3389','0.0.0.0/0')
+ template.add_rule_to_securitygroup(sec_grp, rule)
+ template.add_resource('ADSecurityGroup', sec_grp)
+
+ instance = template.create_instance()
+ instance_name= 'AD-DC001'
+ template.add_security_group(instance, 'ADSecurityGroup')
+ template.add_resource(instance_name, instance)
+
+ template.add_output_value(instance_name+'-IP',{"Fn::GetAtt" : [instance_name,'PublicIp']},
+ 'Public IP for the domain controller.')
+ context['template']=template
+ pass
+
+ def deploy_template_command(self, context, event, data):
+ LOG.debug("Creating CloudFormation Template deployment command...")
+ fname = "templates/"+str(uuid.uuid4())
+ f=open(fname, "w")
+ f.write(context['template'].to_json())
+ f.close()
+ context['template_name']=fname
+ command = command_api.Command(command_api.TEMPLATE_DEPLOYMENT_COMMAND, context)
+ return command
+ pass
+
+ def chef_configuration(self, context, event, data):
+ LOG.debug("Creating Chef configuration...")
+ context['Role'] = 'pdc'
+ pass
+
+ def chef_configuration_command(self, context, event, data):
+ LOG.debug("Creating Chef configuration command...")
+ command = command_api.Command(command_api.CHEF_COMMAND, context)
+ return command
+
+ def submit_commands(self, context, event, data):
+ LOG.debug("Submit commands for execution...")
+ pass
\ No newline at end of file
diff --git a/windc/windc/core/change_events.py b/windc/windc/core/change_events.py
index 8324a7f..8955b58 100644
--- a/windc/windc/core/change_events.py
+++ b/windc/windc/core/change_events.py
@@ -20,6 +20,8 @@ import logging
LOG = logging.getLogger(__name__)
from windc.core import builder_set
+from windc.core import builder
+from windc.drivers import command_executor
#Declare events types
SCOPE_SERVICE_CHANGE = "Service"
@@ -40,11 +42,14 @@ class Event:
def change_event(conf, event, data):
LOG.info("Change event of type: %s ", event)
- context = {}
+ context = builder.create_context()
context['conf'] = conf
for builder_type in builder_set.builders.set:
- builder = builder_set.builders.set[builder_type]
- builder.build(context, event, data)
+ builder_instance = builder_set.builders.set[builder_type]
+ builder_instance.build(context, event, data)
+
+ executor = command_executor.Executor()
+ executor.execute(context['commands'])
pass
diff --git a/windc/windc/core/commands.py b/windc/windc/core/commands.py
new file mode 100644
index 0000000..86473c1
--- /dev/null
+++ b/windc/windc/core/commands.py
@@ -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
+
+
diff --git a/windc/windc/core/templates.py b/windc/windc/core/templates.py
new file mode 100644
index 0000000..47a7c90
--- /dev/null
+++ b/windc/windc/core/templates.py
@@ -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
+
+
+
+
diff --git a/windc/windc/db/api.py b/windc/windc/db/api.py
index d01c3ca..1e06f78 100644
--- a/windc/windc/db/api.py
+++ b/windc/windc/db/api.py
@@ -88,7 +88,8 @@ def datacenter_create(conf, values):
def datacenter_update(conf, datacenter_id, values):
session = get_session(conf)
with session.begin():
- datacenter_ref = datacenter_get(conf, datacenter_id, session=session)
+ datacenter_ref = session.query(models.DataCenter).\
+ filter_by(id=datacenter_id).first()
datacenter_ref.update(values)
return datacenter_ref
@@ -96,8 +97,10 @@ def datacenter_update(conf, datacenter_id, values):
def datacenter_destroy(conf, datacenter_id):
session = get_session(conf)
with session.begin():
- datacenter_ref = device_get(conf, datacenter_id, session=session)
+ datacenter_ref = session.query(models.DataCenter).\
+ filter_by(id=datacenter_id).first()
session.delete(datacenter_ref)
+ return datacenter_ref
# Service
@@ -112,31 +115,10 @@ def service_get(conf, service_id, tenant_id=None, session=None):
raise exception.ServiceNotFound(service_ref=service_ref)
return service_ref
-
-def service_get_all_by_project(conf, tenant_id):
- session = get_session(conf)
- query = session.query(models.Service).filter_by(tenant_id=tenant_id)
- return query.all()
-
-
-def service_get_all_by_vm_id(conf, tenant_id, vm_id):
- session = get_session(conf)
- query = session.query(models.Service).distinct().\
- filter_by(tenant_id=tenant_id).\
- filter(vm_id == vm_id)
- return query.all()
-
-
def service_get_all_by_datacenter_id(conf, tenant_id, datacenter_id):
session = get_session(conf)
query = session.query(models.Service).filter_by(datacenter_id=datacenter_id)
- service_refs = query.all()
- if not service_refs:
- raise exception.ServiceNotFound('No service '
- 'for the datacenter %s found'
- % datacenter_id)
- return service_refs
-
+ return query.all()
def service_create(conf, values):
session = get_session(conf)
@@ -146,7 +128,6 @@ def service_create(conf, values):
session.add(service_ref)
return service_ref
-
def service_update(conf, service_id, values):
session = get_session(conf)
with session.begin():
@@ -155,13 +136,23 @@ def service_update(conf, service_id, values):
service_ref['updated_at'] = datetime.datetime.utcnow()
return service_ref
-
def service_destroy(conf, service_id):
session = get_session(conf)
with session.begin():
service_ref = service_get(conf, service_id, session=session)
session.delete(service_ref)
+def service_get_all_by_project(conf, tenant_id):
+ session = get_session(conf)
+ query = session.query(models.Service).filter_by(tenant_id=tenant_id)
+ return query.all()
+
+def service_get_all_by_vm_id(conf, tenant_id, vm_id):
+ session = get_session(conf)
+ query = session.query(models.Service).distinct().\
+ filter_by(tenant_id=tenant_id).\
+ filter(vm_id == vm_id)
+ return query.all()
def service_count_active_by_datacenter(conf, datacenter_id):
session = get_session(conf)
@@ -171,5 +162,3 @@ def service_count_active_by_datacenter(conf, datacenter_id):
filter_by(status=service_status.ACTIVE).\
count()
return service_count
-
-
diff --git a/windc/windc/drivers/command_executor.py b/windc/windc/drivers/command_executor.py
new file mode 100644
index 0000000..c7c0d2f
--- /dev/null
+++ b/windc/windc/drivers/command_executor.py
@@ -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)
+
+
diff --git a/windc/windc/drivers/openstack_heat.py b/windc/windc/drivers/openstack_heat.py
new file mode 100644
index 0000000..7661bb6
--- /dev/null
+++ b/windc/windc/drivers/openstack_heat.py
@@ -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
+