mirror of
https://github.com/dkmstr/openuds.git
synced 2024-12-22 13:34:04 +03:00
Starting kvm (oVirt based) part
This commit is contained in:
parent
a4c1f6af8f
commit
8a04db28e9
203
server/src/uds/migrations/0007_auto__add_field_config_long.py
Normal file
203
server/src/uds/migrations/0007_auto__add_field_config_long.py
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import SchemaMigration
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(SchemaMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
# Adding field 'Config.long'
|
||||||
|
db.add_column('uds_configuration', 'long',
|
||||||
|
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Deleting field 'Config.long'
|
||||||
|
db.delete_column('uds_configuration', 'long')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'uds.authenticator': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'object_name': 'Authenticator'},
|
||||||
|
'comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||||
|
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||||
|
'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
|
||||||
|
'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
|
||||||
|
},
|
||||||
|
'uds.cache': {
|
||||||
|
'Meta': {'object_name': 'Cache', 'db_table': "'uds_utility_cache'"},
|
||||||
|
'created': ('django.db.models.fields.DateTimeField', [], {}),
|
||||||
|
'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
|
||||||
|
'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||||
|
'validity': ('django.db.models.fields.IntegerField', [], {'default': '60'}),
|
||||||
|
'value': ('django.db.models.fields.TextField', [], {'default': "''"})
|
||||||
|
},
|
||||||
|
'uds.config': {
|
||||||
|
'Meta': {'unique_together': "(('section', 'key'),)", 'object_name': 'Config', 'db_table': "'uds_configuration'"},
|
||||||
|
'crypt': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||||
|
'long': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'section': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'value': ('django.db.models.fields.TextField', [], {'default': "''"})
|
||||||
|
},
|
||||||
|
'uds.delayedtask': {
|
||||||
|
'Meta': {'object_name': 'DelayedTask'},
|
||||||
|
'execution_delay': ('django.db.models.fields.PositiveIntegerField', [], {}),
|
||||||
|
'execution_time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'insert_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'instance': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'tag': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
|
||||||
|
'type': ('django.db.models.fields.CharField', [], {'max_length': '128'})
|
||||||
|
},
|
||||||
|
'uds.deployedservice': {
|
||||||
|
'Meta': {'object_name': 'DeployedService', 'db_table': "'uds__deployed_service'"},
|
||||||
|
'assignedGroups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_grps'", 'to': "orm['uds.Group']"}),
|
||||||
|
'cache_l1_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'cache_l2_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
|
||||||
|
'current_pub_revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'initial_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'max_srvs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
|
||||||
|
'osmanager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.OSManager']"}),
|
||||||
|
'service': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deployedServices'", 'null': 'True', 'to': "orm['uds.Service']"}),
|
||||||
|
'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
|
||||||
|
'state_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
|
||||||
|
'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'deployedServices'", 'symmetrical': 'False', 'db_table': "'uds__ds_trans'", 'to': "orm['uds.Transport']"})
|
||||||
|
},
|
||||||
|
'uds.deployedservicepublication': {
|
||||||
|
'Meta': {'ordering': "('publish_date',)", 'object_name': 'DeployedServicePublication', 'db_table': "'uds__deployed_service_pub'"},
|
||||||
|
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||||
|
'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'publications'", 'to': "orm['uds.DeployedService']"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'publish_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
|
||||||
|
'revision': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
|
||||||
|
'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
|
||||||
|
'state_date': ('django.db.models.fields.DateTimeField', [], {})
|
||||||
|
},
|
||||||
|
'uds.group': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'unique_together': "(('manager', 'name'),)", 'object_name': 'Group'},
|
||||||
|
'comments': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['uds.Authenticator']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||||
|
'state': ('django.db.models.fields.CharField', [], {'default': "'A'", 'max_length': '1', 'db_index': 'True'}),
|
||||||
|
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['uds.User']"})
|
||||||
|
},
|
||||||
|
'uds.network': {
|
||||||
|
'Meta': {'object_name': 'Network'},
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
|
||||||
|
'net_end': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
|
||||||
|
'net_start': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
|
||||||
|
'transports': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'networks'", 'symmetrical': 'False', 'db_table': "'uds_net_trans'", 'to': "orm['uds.Transport']"})
|
||||||
|
},
|
||||||
|
'uds.osmanager': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'object_name': 'OSManager'},
|
||||||
|
'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
|
||||||
|
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||||
|
'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
|
||||||
|
},
|
||||||
|
'uds.provider': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'object_name': 'Provider'},
|
||||||
|
'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
|
||||||
|
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||||
|
'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
|
||||||
|
},
|
||||||
|
'uds.scheduler': {
|
||||||
|
'Meta': {'object_name': 'Scheduler'},
|
||||||
|
'frecuency': ('django.db.models.fields.PositiveIntegerField', [], {'default': '86400'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'last_execution': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
|
||||||
|
'next_execution': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)', 'db_index': 'True'}),
|
||||||
|
'owner_server': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True'}),
|
||||||
|
'state': ('django.db.models.fields.CharField', [], {'default': "'X'", 'max_length': '1', 'db_index': 'True'})
|
||||||
|
},
|
||||||
|
'uds.service': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'unique_together': "(('provider', 'name'),)", 'object_name': 'Service'},
|
||||||
|
'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
|
||||||
|
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||||
|
'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'services'", 'to': "orm['uds.Provider']"})
|
||||||
|
},
|
||||||
|
'uds.storage': {
|
||||||
|
'Meta': {'object_name': 'Storage'},
|
||||||
|
'attr1': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '64', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||||
|
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||||
|
'key': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}),
|
||||||
|
'owner': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
|
||||||
|
},
|
||||||
|
'uds.transport': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'object_name': 'Transport'},
|
||||||
|
'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
|
||||||
|
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||||
|
'data_type': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}),
|
||||||
|
'nets_positive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'priority': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
|
||||||
|
},
|
||||||
|
'uds.uniqueid': {
|
||||||
|
'Meta': {'ordering': "('-seq',)", 'unique_together': "(('basename', 'seq'),)", 'object_name': 'UniqueId'},
|
||||||
|
'assigned': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
|
||||||
|
'basename': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'owner': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
|
||||||
|
'seq': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
|
||||||
|
},
|
||||||
|
'uds.user': {
|
||||||
|
'Meta': {'ordering': "('name',)", 'unique_together': "(('manager', 'name'),)", 'object_name': 'User'},
|
||||||
|
'comments': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
|
||||||
|
'manager': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'users'", 'to': "orm['uds.Authenticator']"}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||||
|
'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
|
||||||
|
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'staff_member': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'state': ('django.db.models.fields.CharField', [], {'max_length': '1', 'db_index': 'True'})
|
||||||
|
},
|
||||||
|
'uds.userpreference': {
|
||||||
|
'Meta': {'object_name': 'UserPreference'},
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'module': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'preferences'", 'to': "orm['uds.User']"}),
|
||||||
|
'value': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'})
|
||||||
|
},
|
||||||
|
'uds.userservice': {
|
||||||
|
'Meta': {'ordering': "('creation_date',)", 'object_name': 'UserService', 'db_table': "'uds__user_service'"},
|
||||||
|
'cache_level': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'db_index': 'True'}),
|
||||||
|
'creation_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
|
||||||
|
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||||
|
'deployed_service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userServices'", 'to': "orm['uds.DeployedService']"}),
|
||||||
|
'friendly_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'in_use': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'in_use_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1972, 7, 1, 0, 0)'}),
|
||||||
|
'os_state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1'}),
|
||||||
|
'publication': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'userServices'", 'null': 'True', 'to': "orm['uds.DeployedServicePublication']"}),
|
||||||
|
'state': ('django.db.models.fields.CharField', [], {'default': "'P'", 'max_length': '1', 'db_index': 'True'}),
|
||||||
|
'state_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'unique_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'db_index': 'True'}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'userServices'", 'null': 'True', 'blank': 'True', 'to': "orm['uds.User']"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['uds']
|
@ -1438,6 +1438,7 @@ class Config(models.Model):
|
|||||||
key = models.CharField(max_length=64)
|
key = models.CharField(max_length=64)
|
||||||
value = models.TextField(default = '')
|
value = models.TextField(default = '')
|
||||||
crypt = models.BooleanField(default = False)
|
crypt = models.BooleanField(default = False)
|
||||||
|
long = models.BooleanField(default = False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
'''
|
'''
|
||||||
|
355
server/src/uds/services/OVirt/OVirtLinkedDeployment.py
Normal file
355
server/src/uds/services/OVirt/OVirtLinkedDeployment.py
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012 Virtual Cable S.L.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
'''
|
||||||
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from uds.core.services import UserDeployment
|
||||||
|
from uds.core.util.State import State
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class OVirtLinkedDeployment(UserDeployment):
|
||||||
|
'''
|
||||||
|
This class generates the user consumable elements of the service tree.
|
||||||
|
|
||||||
|
After creating at administration interface an Deployed Service, UDS will
|
||||||
|
create consumable services for users using UserDeployment class as
|
||||||
|
provider of this elements.
|
||||||
|
|
||||||
|
The logic for managing ovirt deployments (user machines in this case) is here.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
#: Recheck every five seconds by default (for task methods)
|
||||||
|
suggestedTime = 5
|
||||||
|
|
||||||
|
# Serializable needed methods
|
||||||
|
def marshal(self):
|
||||||
|
'''
|
||||||
|
Does nothing right here, we will use envoronment storage in this sample
|
||||||
|
'''
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def unmarshal(self, str_):
|
||||||
|
'''
|
||||||
|
Does nothing here also, all data are keeped at environment storage
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def getName(self):
|
||||||
|
'''
|
||||||
|
We override this to return a name to display. Default inplementation
|
||||||
|
(in base class), returns getUniqueIde() value
|
||||||
|
This name will help user to identify elements, and is only used
|
||||||
|
at administration interface.
|
||||||
|
|
||||||
|
We will use here the environment name provided generator to generate
|
||||||
|
a name for this element.
|
||||||
|
|
||||||
|
The namaGenerator need two params, the base name and a length for a
|
||||||
|
numeric incremental part for generating unique names. This are unique for
|
||||||
|
all UDS names generations, that is, UDS will not generate this name again
|
||||||
|
until this name is freed, or object is removed, what makes its environment
|
||||||
|
to also get removed, that makes all uniques ids (names and macs right now)
|
||||||
|
to also get released.
|
||||||
|
|
||||||
|
Every time get method of a generator gets called, the generator creates
|
||||||
|
a new unique name, so we keep the first generated name cached and don't
|
||||||
|
generate more names. (Generator are simple utility classes)
|
||||||
|
'''
|
||||||
|
name = self.storage().readData('name')
|
||||||
|
if name is None:
|
||||||
|
name = self.nameGenerator().get( self.service().getBaseName()
|
||||||
|
+ '-' + self.service().getColour(), 3 )
|
||||||
|
# Store value for persistence
|
||||||
|
self.storage().saveData('name', name)
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
def setIp(self, ip):
|
||||||
|
'''
|
||||||
|
In our case, there is no OS manager associated with this, so this method
|
||||||
|
will never get called, but we put here as sample.
|
||||||
|
|
||||||
|
Whenever an os manager actor notifies the broker the state of the service
|
||||||
|
(mainly machines), the implementation of that os manager can (an probably will)
|
||||||
|
need to notify the IP of the deployed service. Remember that UDS treats with
|
||||||
|
IP services, so will probable needed in every service that you will create.
|
||||||
|
:note: This IP is the IP of the "consumed service", so the transport can
|
||||||
|
access it.
|
||||||
|
'''
|
||||||
|
self.storage().saveData('ip', str(ip))
|
||||||
|
|
||||||
|
def getUniqueId(self):
|
||||||
|
'''
|
||||||
|
Return and unique identifier for this service.
|
||||||
|
In our case, we will generate a mac name, that can be also as sample
|
||||||
|
of 'mac' generator use, and probably will get used something like this
|
||||||
|
at some services.
|
||||||
|
|
||||||
|
The get method of a mac generator takes one param, that is the mac range
|
||||||
|
to use to get an unused mac.
|
||||||
|
'''
|
||||||
|
mac = self.storage().readData('mac')
|
||||||
|
if mac is None:
|
||||||
|
mac = self.macGenerator().get( '00:00:00:00:00:00-00:FF:FF:FF:FF:FF' )
|
||||||
|
self.storage().saveData('mac', mac)
|
||||||
|
return mac
|
||||||
|
|
||||||
|
def getIp(self):
|
||||||
|
'''
|
||||||
|
We need to implement this method, so we can return the IP for transports
|
||||||
|
use. If no IP is known for this service, this must return None
|
||||||
|
|
||||||
|
If our sample do not returns an IP, IP transport will never work with
|
||||||
|
this service. Remember in real cases to return a valid IP address if
|
||||||
|
the service is accesible and you alredy know that (for example, because
|
||||||
|
the IP has been assigend via setIp by an os manager) or because
|
||||||
|
you get it for some other method.
|
||||||
|
|
||||||
|
Storage returns None if key is not stored.
|
||||||
|
|
||||||
|
:note: Keeping the IP address is responsibility of the User Deployment.
|
||||||
|
Every time the core needs to provide the service to the user, or
|
||||||
|
show the IP to the administrator, this method will get called
|
||||||
|
|
||||||
|
'''
|
||||||
|
ip = self.storage().readData('ip')
|
||||||
|
if ip is None:
|
||||||
|
ip = '192.168.0.34' # Sample IP for testing purposses only
|
||||||
|
return ip
|
||||||
|
|
||||||
|
def setReady(self):
|
||||||
|
'''
|
||||||
|
This is a task method. As that, the expected return values are
|
||||||
|
State values RUNNING, FINISHED or ERROR.
|
||||||
|
|
||||||
|
The method is invoked whenever a machine is provided to an user, right
|
||||||
|
before presenting it (via transport rendering) to the user.
|
||||||
|
|
||||||
|
This method exist for this kind of situations (i will explain it with a
|
||||||
|
sample)
|
||||||
|
|
||||||
|
Imagine a Service tree (Provider, Service, ...) for virtual machines.
|
||||||
|
This machines will get created by the UserDeployment implementation, but,
|
||||||
|
at some time, the machine can be put at in an state (suspend, shut down)
|
||||||
|
that will make the transport impossible to connect with it.
|
||||||
|
|
||||||
|
This method, in this case, will check the state of the machine, and if
|
||||||
|
it is "ready", that is, powered on and accesible, it will return
|
||||||
|
"State.FINISHED". If the machine is not accesible (has ben erased, for
|
||||||
|
example), it will return "State.ERROR" and store a reason of error so UDS
|
||||||
|
can ask for it and present this information to the Administrator.
|
||||||
|
|
||||||
|
If the machine powered off, or suspended, or any other state that is not
|
||||||
|
directly usable but can be put in an usable state, it will return
|
||||||
|
"State.RUNNING", and core will use checkState to see when the operation
|
||||||
|
has finished.
|
||||||
|
|
||||||
|
I hope this sample is enough to explain the use of this method..
|
||||||
|
'''
|
||||||
|
|
||||||
|
# In our case, the service is always ready
|
||||||
|
return State.FINISHED
|
||||||
|
|
||||||
|
def deployForUser(self, user):
|
||||||
|
'''
|
||||||
|
Deploys an service instance for an user.
|
||||||
|
|
||||||
|
This is a task method. As that, the excepted return values are
|
||||||
|
State values RUNNING, FINISHED or ERROR.
|
||||||
|
|
||||||
|
The user parameter is not realy neded, but provided. It indicates the
|
||||||
|
Database User Object (see py:mod:`uds.modules`) to which this deployed
|
||||||
|
user service will be assigned to.
|
||||||
|
|
||||||
|
This method will get called whenever a new deployed service for an user
|
||||||
|
is needed. This will give this class the oportunity to create
|
||||||
|
a service that is assigned to an user.
|
||||||
|
|
||||||
|
The way of using this method is as follows:
|
||||||
|
|
||||||
|
If the service gets created in "one step", that is, before the return
|
||||||
|
of this method, the consumable service for the user gets created, it
|
||||||
|
will return "State.FINISH".
|
||||||
|
If the service needs more steps (as in this case), we will return
|
||||||
|
"State.RUNNING", and if it has an error, it wil return "State.ERROR" and
|
||||||
|
store an error string so administration interface can show it.
|
||||||
|
|
||||||
|
We do not use user for anything, as in most cases will be.
|
||||||
|
'''
|
||||||
|
import random
|
||||||
|
|
||||||
|
self.storage().saveData('count', '0')
|
||||||
|
|
||||||
|
# random fail
|
||||||
|
if random.randint(0, 9) == 9:
|
||||||
|
self.storage().saveData('error', 'Random error at deployForUser :-)')
|
||||||
|
return State.ERROR
|
||||||
|
|
||||||
|
return State.RUNNING
|
||||||
|
|
||||||
|
|
||||||
|
def checkState(self):
|
||||||
|
'''
|
||||||
|
Our deployForUser method will initiate the consumable service deployment,
|
||||||
|
but will not finish it.
|
||||||
|
|
||||||
|
So in our sample, we will only check if a number reaches 5, and if so
|
||||||
|
return that we have finished, else we will return that we are working
|
||||||
|
on it.
|
||||||
|
|
||||||
|
One deployForUser returns State.RUNNING, this task will get called until
|
||||||
|
checkState returns State.FINISHED.
|
||||||
|
|
||||||
|
Also, we will make the publication fail one of every 10 calls to this
|
||||||
|
method.
|
||||||
|
|
||||||
|
Note: Destroying, canceling and deploying for cache also makes use of
|
||||||
|
this method, so you must keep the info of that you are checking if you
|
||||||
|
need it.
|
||||||
|
In our case, destroy is 1-step action so this will no get called while
|
||||||
|
destroying, and cancel will simply invoke destroy
|
||||||
|
'''
|
||||||
|
import random
|
||||||
|
|
||||||
|
count = int(self.storage().readData('count')) + 1
|
||||||
|
# Count is always a valid value, because this method will never get
|
||||||
|
# called before deployForUser, deployForCache, destroy or cancel.
|
||||||
|
# In our sample, we only use checkState in case of deployForUser,
|
||||||
|
# so at first call count will be 0.
|
||||||
|
if count >= 5:
|
||||||
|
return State.FINISHED
|
||||||
|
|
||||||
|
# random fail
|
||||||
|
if random.randint(0, 9) == 9:
|
||||||
|
self.storage().saveData('error', 'Random error at checkState :-)')
|
||||||
|
return State.ERROR
|
||||||
|
|
||||||
|
self.storage().saveData('count', str(count))
|
||||||
|
return State.RUNNING
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
'''
|
||||||
|
Invoked when the core notices that the deployment of a service has finished.
|
||||||
|
(No matter wether it is for cache or for an user)
|
||||||
|
|
||||||
|
This gives the oportunity to make something at that moment.
|
||||||
|
:note: You can also make these operations at checkState, this is really
|
||||||
|
not needed, but can be provided (default implementation of base class does
|
||||||
|
nothing)
|
||||||
|
'''
|
||||||
|
# Note that this is not really needed, is just a sample of storage use
|
||||||
|
self.storage().remove('count')
|
||||||
|
|
||||||
|
def assignToUser(self, user):
|
||||||
|
'''
|
||||||
|
This method is invoked whenever a cache item gets assigned to an user.
|
||||||
|
This gives the User Deployment an oportunity to do whatever actions
|
||||||
|
are required so the service puts at a correct state for using by a service.
|
||||||
|
|
||||||
|
In our sample, the service is always ready, so this does nothing.
|
||||||
|
|
||||||
|
This is not a task method. All level 1 cache items can be diretly
|
||||||
|
assigned to an user with no more work needed, but, if something is needed,
|
||||||
|
here you can do whatever you need
|
||||||
|
'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
def userLoggedIn(self, user):
|
||||||
|
'''
|
||||||
|
This method must be available so os managers can invoke it whenever
|
||||||
|
an user get logged into a service.
|
||||||
|
|
||||||
|
Default implementation does nothing, so if you are going to do nothing,
|
||||||
|
you don't need to implement it.
|
||||||
|
|
||||||
|
The responability of notifying it is of os manager actor, and it's
|
||||||
|
directly invoked by os managers (right now, linux os manager and windows
|
||||||
|
os manager)
|
||||||
|
|
||||||
|
The user provided is just an string, that is provided by actor.
|
||||||
|
'''
|
||||||
|
# We store the value at storage, but never get used, just an example
|
||||||
|
self.storage().saveData('user', user)
|
||||||
|
|
||||||
|
def userLoggedOut(self, user):
|
||||||
|
'''
|
||||||
|
This method must be available so os managers can invoke it whenever
|
||||||
|
an user get logged out if a service.
|
||||||
|
|
||||||
|
Default implementation does nothing, so if you are going to do nothing,
|
||||||
|
you don't need to implement it.
|
||||||
|
|
||||||
|
The responability of notifying it is of os manager actor, and it's
|
||||||
|
directly invoked by os managers (right now, linux os manager and windows
|
||||||
|
os manager)
|
||||||
|
|
||||||
|
The user provided is just an string, that is provided by actor.
|
||||||
|
'''
|
||||||
|
# We do nothing more that remove the user
|
||||||
|
self.storage().remove('user')
|
||||||
|
|
||||||
|
def reasonOfError(self):
|
||||||
|
'''
|
||||||
|
Returns the reason of the error.
|
||||||
|
|
||||||
|
Remember that the class is responsible of returning this whenever asked
|
||||||
|
for it, and it will be asked everytime it's needed to be shown to the
|
||||||
|
user (when the administation asks for it).
|
||||||
|
'''
|
||||||
|
return self.storage().readData('error') or 'No error'
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
'''
|
||||||
|
This is a task method. As that, the excepted return values are
|
||||||
|
State values RUNNING, FINISHED or ERROR.
|
||||||
|
|
||||||
|
Invoked for destroying a deployed service
|
||||||
|
Do whatever needed here, as deleting associated data if needed (i.e. a copy of the machine, snapshots, etc...)
|
||||||
|
@return: State.FINISHED if no more checks/steps for deployment are needed, State.RUNNING if more steps are needed (steps checked using checkState)
|
||||||
|
'''
|
||||||
|
return State.FINISHED
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
'''
|
||||||
|
This is a task method. As that, the excepted return values are
|
||||||
|
State values RUNNING, FINISHED or ERROR.
|
||||||
|
|
||||||
|
This can be invoked directly by an administration or by the clean up
|
||||||
|
of the deployed service (indirectly).
|
||||||
|
When administrator requests it, the cancel is "delayed" and not
|
||||||
|
invoked directly.
|
||||||
|
'''
|
||||||
|
return State.FINISHED
|
||||||
|
|
187
server/src/uds/services/OVirt/OVirtLinkedService.py
Normal file
187
server/src/uds/services/OVirt/OVirtLinkedService.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012 Virtual Cable S.L.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
'''
|
||||||
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_noop as translatable, ugettext as _
|
||||||
|
from uds.core.services import Service
|
||||||
|
from OVirtPublication import OVirtPublication
|
||||||
|
from OVirtLinkedDeployment import OVirtLinkedDeployment
|
||||||
|
|
||||||
|
from uds.core.ui import gui
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class OVirtLinkedService(Service):
|
||||||
|
'''
|
||||||
|
Basic service, the first part (variables) include the description of the service.
|
||||||
|
|
||||||
|
Remember to fill all variables needed, but at least you must define:
|
||||||
|
* typeName
|
||||||
|
* typeType
|
||||||
|
* typeDescription
|
||||||
|
* iconFile (defaults to service.png)
|
||||||
|
* publicationType, type of publication in case it needs publication.
|
||||||
|
If this is not provided, core will assume that the service do not
|
||||||
|
needs publishing.
|
||||||
|
* deployedType, type of deployed user service. Do not forget this!!!
|
||||||
|
|
||||||
|
The rest of them can be ommited, but its recommended that you fill all
|
||||||
|
declarations shown in this sample (that in fact, are all)
|
||||||
|
|
||||||
|
This description informs the core what this service really provides,
|
||||||
|
and how this is done. Look at description of class variables for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
'''
|
||||||
|
#: Name to show the administrator. This string will be translated BEFORE
|
||||||
|
#: sending it to administration interface, so don't forget to
|
||||||
|
#: mark it as translatable (using ugettext_noop)
|
||||||
|
typeName = translatable('Sample Service One')
|
||||||
|
#: Type used internally to identify this provider
|
||||||
|
typeType = 'SampleService1'
|
||||||
|
#: Description shown at administration interface for this provider
|
||||||
|
typeDescription = translatable('Sample (and dummy) service ONE')
|
||||||
|
#: Icon file used as icon for this provider. This string will be translated
|
||||||
|
#: BEFORE sending it to administration interface, so don't forget to
|
||||||
|
#: mark it as translatable (using ugettext_noop)
|
||||||
|
iconFile = 'service.png'
|
||||||
|
|
||||||
|
# Functional related data
|
||||||
|
|
||||||
|
#: If the service provides more than 1 "deployed user" (-1 = no limit,
|
||||||
|
#: 0 = ???? (do not use it!!!), N = max number to deploy
|
||||||
|
maxDeployed = -1
|
||||||
|
#: If we need to generate "cache" for this service, so users can access the
|
||||||
|
#: provided services faster. Is usesCache is True, you will need also
|
||||||
|
#: set publicationType, do take care about that!
|
||||||
|
usesCache = False
|
||||||
|
#: Tooltip shown to user when this item is pointed at admin interface, none
|
||||||
|
#: because we don't use it
|
||||||
|
cacheTooltip = translatable('None')
|
||||||
|
#: If we need to generate a "Level 2" cache for this service (i.e., L1
|
||||||
|
#: could be running machines and L2 suspended machines)
|
||||||
|
usesCache_L2 = False
|
||||||
|
#: Tooltip shown to user when this item is pointed at admin interface, None
|
||||||
|
#: also because we don't use it
|
||||||
|
cacheTooltip_L2 = translatable('None')
|
||||||
|
|
||||||
|
#: If the service needs a s.o. manager (managers are related to agents
|
||||||
|
#: provided by services itselfs, i.e. virtual machines with actors)
|
||||||
|
needsManager = False
|
||||||
|
#: If true, the system can't do an automatic assignation of a deployed user
|
||||||
|
#: service from this service
|
||||||
|
mustAssignManually = False
|
||||||
|
|
||||||
|
#: Types of publications (preparated data for deploys)
|
||||||
|
#: In our case, we do no need a publication, so this is None
|
||||||
|
publicationType = None
|
||||||
|
#: Types of deploys (services in cache and/or assigned to users)
|
||||||
|
deployedType = OVirtLinkedDeployment
|
||||||
|
|
||||||
|
# Now the form part, this service will have only two "dummy" fields
|
||||||
|
# If we don't indicate an order, the output order of fields will be
|
||||||
|
# "random"
|
||||||
|
|
||||||
|
colour = gui.ChoiceField(order = 1,
|
||||||
|
label = translatable('Colour'),
|
||||||
|
tooltip = translatable('Colour of the field'),
|
||||||
|
# In this case, the choice can have none value selected by default
|
||||||
|
required = True,
|
||||||
|
values = [ gui.choiceItem('red', 'Red'),
|
||||||
|
gui.choiceItem('green', 'Green'),
|
||||||
|
gui.choiceItem('blue', 'Blue'),
|
||||||
|
gui.choiceItem('nonsense', 'Blagenta')
|
||||||
|
],
|
||||||
|
defvalue = '1' # Default value is the ID of the choicefield
|
||||||
|
)
|
||||||
|
|
||||||
|
passw = gui.PasswordField(order = 2,
|
||||||
|
label = translatable('Password'),
|
||||||
|
tooltip = translatable('Password for testing purposes'),
|
||||||
|
required = True,
|
||||||
|
defvalue = '1234' #: Default password are nonsense?? :-)
|
||||||
|
)
|
||||||
|
|
||||||
|
baseName = gui.TextField(order = 3,
|
||||||
|
label = translatable('Services names'),
|
||||||
|
tooltip = translatable('Base name for this user services'),
|
||||||
|
# In this case, the choice can have none value selected by default
|
||||||
|
required = True,
|
||||||
|
defvalue = '' # Default value is the ID of the choicefield
|
||||||
|
)
|
||||||
|
|
||||||
|
def initialize(self, values):
|
||||||
|
'''
|
||||||
|
We check here form values to see if they are valid.
|
||||||
|
|
||||||
|
Note that we check them throught FROM variables, that already has been
|
||||||
|
initialized by __init__ method of base class, before invoking this.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# We don't need to check anything, bat because this is a sample, we do
|
||||||
|
# As in provider, we receive values only at new Service creation,
|
||||||
|
# so we only need to validate params if values is not None
|
||||||
|
if values is not None:
|
||||||
|
if self.colour.value == 'nonsense':
|
||||||
|
raise Service.ValidationException('The selected colour is invalid!!!')
|
||||||
|
|
||||||
|
|
||||||
|
# Services itself are non testeable right now, so we don't even have
|
||||||
|
# to provide one!!!
|
||||||
|
|
||||||
|
|
||||||
|
# Congratulations!!!, the needed part of your first simple service is done!
|
||||||
|
# Now you can go to administration panel, and check it
|
||||||
|
#
|
||||||
|
# From now onwards, we implement our own methods, that will be used by,
|
||||||
|
# for example, services derived from this provider
|
||||||
|
|
||||||
|
def getColour(self):
|
||||||
|
'''
|
||||||
|
Simply returns colour, for deployed user services.
|
||||||
|
|
||||||
|
Remember that choiceField.value returns the id part of the ChoiceItem
|
||||||
|
'''
|
||||||
|
return self.colour.value
|
||||||
|
|
||||||
|
def getPassw(self):
|
||||||
|
'''
|
||||||
|
Simply returns passwd, for deloyed user services
|
||||||
|
'''
|
||||||
|
return self.passw.value
|
||||||
|
|
||||||
|
def getBaseName(self):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
return self.baseName.value
|
166
server/src/uds/services/OVirt/OVirtProvider.py
Normal file
166
server/src/uds/services/OVirt/OVirtProvider.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012 Virtual Cable S.L.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
'''
|
||||||
|
Created on Jun 22, 2012
|
||||||
|
|
||||||
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_noop as translatable, ugettext as _
|
||||||
|
from uds.core.services import ServiceProvider
|
||||||
|
from OVirtLinkedService import OVirtLinkedService
|
||||||
|
from uds.core.ui import gui
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Provider(ServiceProvider):
|
||||||
|
'''
|
||||||
|
This class represents the sample services provider
|
||||||
|
|
||||||
|
In this class we provide:
|
||||||
|
* The Provider functionality
|
||||||
|
* The basic configuration parameters for the provider
|
||||||
|
* The form fields needed by administrators to configure this provider
|
||||||
|
|
||||||
|
:note: At class level, the translation must be simply marked as so
|
||||||
|
using ugettext_noop. This is so cause we will translate the string when
|
||||||
|
sent to the administration client.
|
||||||
|
|
||||||
|
For this class to get visible at administration client as a provider type,
|
||||||
|
we MUST register it at package __init__.
|
||||||
|
|
||||||
|
'''
|
||||||
|
#: What kind of services we offer, this are classes inherited from Service
|
||||||
|
offers = [OVirtLinkedService]
|
||||||
|
#: Name to show the administrator. This string will be translated BEFORE
|
||||||
|
#: sending it to administration interface, so don't forget to
|
||||||
|
#: mark it as translatable (using ugettext_noop)
|
||||||
|
typeName = translatable('oVirt Platform Provider')
|
||||||
|
#: Type used internally to identify this provider
|
||||||
|
typeType = 'oVirtPlatform'
|
||||||
|
#: Description shown at administration interface for this provider
|
||||||
|
typeDescription = translatable('oVirt platform service provider')
|
||||||
|
#: Icon file used as icon for this provider. This string will be translated
|
||||||
|
#: BEFORE sending it to administration interface, so don't forget to
|
||||||
|
#: mark it as translatable (using ugettext_noop)
|
||||||
|
iconFile = 'provider.png'
|
||||||
|
|
||||||
|
# now comes the form fields
|
||||||
|
# There is always two fields that are requested to the admin, that are:
|
||||||
|
# Service Name, that is a name that the admin uses to name this provider
|
||||||
|
# Description, that is a short description that the admin gives to this provider
|
||||||
|
# Now we are going to add a few fields that we need to use this provider
|
||||||
|
# Remember that these are "dummy" fields, that in fact are not required
|
||||||
|
# but used for sample purposes
|
||||||
|
# If we don't indicate an order, the output order of fields will be
|
||||||
|
# "random"
|
||||||
|
host = gui.TextField(length=64, label = _('Host'), order = 1, tooltip = _('oVirt Server IP or Hostname'), required = True)
|
||||||
|
port = gui.NumericField(length=5, label = _('Port'), defvalue = '443', order = 2, tooltip = _('VMWare VC Server Port (usually 443)'), required = True)
|
||||||
|
username = gui.TextField(length=32, label = _('Username'), order = 3, tooltip = _('User with valid privileges on VC'), required = True)
|
||||||
|
password = gui.PasswordField(lenth=32, label = _('Password'), order = 4, tooltip = _('Password of the user of the VC'), required = True)
|
||||||
|
timeout = gui.NumericField(length=3, label = _('Timeout'), defvalue = '10', order = 5, tooltip = _('Timeout in seconds of connection to VC'), required = True)
|
||||||
|
macsRange = gui.TextField(length=36, label = _('Macs range'), defvalue = '00:50:56:00:00:00-00:50:56:3F:FF:FF', order = 6, rdonly = True,
|
||||||
|
tooltip = _('Range of valids macs for created machines'), required = True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# There is more fields type, but not here the best place to cover it
|
||||||
|
def initialize(self, values = None):
|
||||||
|
'''
|
||||||
|
We will use the "autosave" feature for form fields, that is more than
|
||||||
|
enought for most providers. (We simply need to store data provided by user
|
||||||
|
and, maybe, initialize some kind of connection with this values).
|
||||||
|
|
||||||
|
Normally provider values are rally used at sevice level, cause we never
|
||||||
|
instantiate nothing except a service from a provider.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# If you say meth is alive, you are wrong!!! (i guess..)
|
||||||
|
# values are only passed from administration client. Internals
|
||||||
|
# instantiations are always empty.
|
||||||
|
#if values is not None and self.methAlive.isTrue():
|
||||||
|
# raise ServiceProvider.ValidationException(_('Methuselah is not alive!!! :-)'))
|
||||||
|
|
||||||
|
# Marshal and unmarshal are defaults ones, also enought
|
||||||
|
|
||||||
|
# As we use "autosave" fields feature, dictValues is also provided by
|
||||||
|
# base class so we don't have to mess with all those things...
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def test(env, data):
|
||||||
|
'''
|
||||||
|
Create your test method here so the admin can push the "check" button
|
||||||
|
and this gets executed.
|
||||||
|
Args:
|
||||||
|
env: environment passed for testing (temporal environment passed)
|
||||||
|
|
||||||
|
data: data passed for testing (data obtained from the form
|
||||||
|
definition)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Array of two elements, first is True of False, depending on test
|
||||||
|
(True is all right, false is error),
|
||||||
|
second is an String with error, preferably internacionalizated..
|
||||||
|
|
||||||
|
In this case, wi well do nothing more that use the provider params
|
||||||
|
|
||||||
|
Note also that this is an static method, that will be invoked using
|
||||||
|
the admin user provided data via administration client, and a temporary
|
||||||
|
environment that will be erased after invoking this method
|
||||||
|
'''
|
||||||
|
#try:
|
||||||
|
# # We instantiate the provider, but this may fail...
|
||||||
|
# instance = Provider(env, data)
|
||||||
|
# logger.debug('Methuselah has {0} years and is {1} :-)'
|
||||||
|
# .format(instance.methAge.value, instance.methAlive.value))
|
||||||
|
#except ServiceProvider.ValidationException as e:
|
||||||
|
# # If we say that meth is alive, instantiation will
|
||||||
|
# return [False, str(e)]
|
||||||
|
#except Exception as e:
|
||||||
|
# logger.exception("Exception caugth!!!")
|
||||||
|
# return [False, str(e)]
|
||||||
|
#return [True, _('Nothing tested, but all went fine..')]
|
||||||
|
return [True, _('Connection test successful')]
|
||||||
|
|
||||||
|
# Congratulations!!!, the needed part of your first simple provider is done!
|
||||||
|
# Now you can go to administration panel, and check it
|
||||||
|
#
|
||||||
|
# From now onwards, we implement our own methods, that will be used by,
|
||||||
|
# for example, services derived from this provider
|
||||||
|
def host(self):
|
||||||
|
'''
|
||||||
|
Sample method, in fact in this we just return
|
||||||
|
the value of host field, that is an string
|
||||||
|
'''
|
||||||
|
return self.remoteHost.value
|
||||||
|
|
272
server/src/uds/services/OVirt/OVirtPublication.py
Normal file
272
server/src/uds/services/OVirt/OVirtPublication.py
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012 Virtual Cable S.L.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
'''
|
||||||
|
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
|
'''
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from uds.core.services import Publication
|
||||||
|
from uds.core.util.State import State
|
||||||
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class OVirtPublication(Publication):
|
||||||
|
'''
|
||||||
|
This class shows how a publication is developed.
|
||||||
|
|
||||||
|
In order to a publication to work correctly, we must provide at least the
|
||||||
|
following methods:
|
||||||
|
* Of course, the __init__
|
||||||
|
* :py:meth:`.publish`
|
||||||
|
* :py:meth:`.checkState`
|
||||||
|
* :py:meth:`.finish`
|
||||||
|
|
||||||
|
Also, of course, methods from :py:class:`uds.core.Serializable.Serializable`
|
||||||
|
|
||||||
|
|
||||||
|
Publication do not have an configuration interface, all data contained
|
||||||
|
inside an instance of a Publication must be serialized if you want them between
|
||||||
|
method calls.
|
||||||
|
|
||||||
|
It's not waranteed that the class will not be serialized/deserialized
|
||||||
|
between methods calls, so, first of all, implement the marshal and umnarshal
|
||||||
|
mehods needed by all serializable classes.
|
||||||
|
|
||||||
|
Also a thing to note is that operations requested to Publications must be
|
||||||
|
*as fast as posible*. The operations executes in a separated thread,
|
||||||
|
and so it cant take a bit more time to execute, but it's recommended that
|
||||||
|
the operations executes as fast as posible, and, if it will take a long time,
|
||||||
|
split operation so we can keep track of state.
|
||||||
|
|
||||||
|
This means that, if we have "slow" operations, we must
|
||||||
|
|
||||||
|
We first of all declares an estimation of how long a publication will take.
|
||||||
|
This value is instance based, so if we override it in our class, the suggested
|
||||||
|
time could change.
|
||||||
|
|
||||||
|
The class attribute that indicates this suggested time is "suggestedTime", and
|
||||||
|
it's expressed in seconds, (i.e. "suggestedTime = 10")
|
||||||
|
'''
|
||||||
|
|
||||||
|
suggestedTime = 5 #: Suggested recheck time if publication is unfinished in seconds
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
'''
|
||||||
|
This method will be invoked by default __init__ of base class, so it gives
|
||||||
|
us the oportunity to initialize whataver we need here.
|
||||||
|
|
||||||
|
In our case, we setup a few attributes..
|
||||||
|
'''
|
||||||
|
|
||||||
|
# We do not check anything at marshal method, so we ensure that
|
||||||
|
# default values are correctly handled by marshal.
|
||||||
|
self._name = 'test'
|
||||||
|
self._reason = '' # No error, no reason for it
|
||||||
|
self._number = 1
|
||||||
|
|
||||||
|
def marshal(self):
|
||||||
|
'''
|
||||||
|
returns data from an instance of Sample Publication serialized
|
||||||
|
'''
|
||||||
|
return '\t'.join( [self._name, self._reason, str(self._number)] )
|
||||||
|
|
||||||
|
def unmarshal(self, data):
|
||||||
|
'''
|
||||||
|
deserializes the data and loads it inside instance.
|
||||||
|
'''
|
||||||
|
logger.debug('Data: {0}'.format(data))
|
||||||
|
vals = data.split('\t')
|
||||||
|
logger.debug('Values: {0}'.format(vals))
|
||||||
|
self._name = vals[0]
|
||||||
|
self._reason = vals[1]
|
||||||
|
self._number = int(vals[2])
|
||||||
|
|
||||||
|
|
||||||
|
def publish(self):
|
||||||
|
'''
|
||||||
|
This method is invoked whenever the administrator requests a new publication.
|
||||||
|
|
||||||
|
The method is not invoked directly (i mean, that the administration request
|
||||||
|
do no makes a call to this method), but a DelayedTask is saved witch will
|
||||||
|
initiate all publication stuff (and, of course, call this method).
|
||||||
|
|
||||||
|
You MUST implement it, so the publication do really something.
|
||||||
|
All publications can be synchronous or asynchronous.
|
||||||
|
|
||||||
|
The main difference between both is that first do whatever needed, (the
|
||||||
|
action must be fast enough to do not block core), returning State.FINISHED.
|
||||||
|
|
||||||
|
The second (asynchronous) are publications that could block the core, so
|
||||||
|
it have to be done in more than one step.
|
||||||
|
|
||||||
|
An example publication could be a copy of a virtual machine, where:
|
||||||
|
* First we invoke the copy operation to virtualization provider
|
||||||
|
* Second, we kept needed values inside instance so we can serialize
|
||||||
|
them whenever requested
|
||||||
|
* Returns an State.RUNNING, indicating the core that the publication
|
||||||
|
has started but has to finish sometime later. (We do no check
|
||||||
|
again the state and keep waiting here, because we will block the
|
||||||
|
core untill this operation is finished).
|
||||||
|
|
||||||
|
In our example wi will simple assign a name, and set number to 5. We
|
||||||
|
will use this number later, to make a "delay" at check if the publication
|
||||||
|
has finished. (see method checkState)
|
||||||
|
|
||||||
|
We also will make this publication an "stepped one", that is, it will not
|
||||||
|
finish at publish call but a later checkState call
|
||||||
|
|
||||||
|
Take care with instantiating threads from here. Whenever a publish returns
|
||||||
|
"State.RUNNING", the core will recheck it later, but not using this instance
|
||||||
|
and maybe that even do not use this server.
|
||||||
|
|
||||||
|
If you want to use threadings or somethin likt it, use DelayedTasks and
|
||||||
|
do not block it. You also musht provide the mechanism to allow those
|
||||||
|
DelayedTask to communicate with the publication.
|
||||||
|
|
||||||
|
One sample could be, for example, to copy a bunch of files, but we know
|
||||||
|
that this copy can take a long time and don't want it to take make it
|
||||||
|
all here, but in a separate task. Now, do you remember that "environment"
|
||||||
|
that is unique for every instance?, well, we can create a delayed task,
|
||||||
|
and pass that environment (owned by this intance) as a mechanism for
|
||||||
|
informing when the task is finished. (We insert at delayed tasks queue
|
||||||
|
an instance, not a class itself, so we can instantiate a class and
|
||||||
|
store it at delayed task queue.
|
||||||
|
|
||||||
|
Also note that, in that case, this class can also acomplish that by simply
|
||||||
|
using the suggestedTime attribute and the checkState method in most cases.
|
||||||
|
'''
|
||||||
|
self._number = 5
|
||||||
|
self._reason = ''
|
||||||
|
return State.RUNNING
|
||||||
|
|
||||||
|
def checkState(self):
|
||||||
|
'''
|
||||||
|
Our publish method will initiate publication, but will not finish it.
|
||||||
|
So in our sample, wi will only check if _number reaches 0, and if so
|
||||||
|
return that we have finished, else we will return that we are working
|
||||||
|
on it.
|
||||||
|
|
||||||
|
One publish returns State.RUNNING, this task will get called untill
|
||||||
|
checkState returns State.FINISHED.
|
||||||
|
|
||||||
|
Also, wi will make the publication fail one of every 10 calls to this
|
||||||
|
method.
|
||||||
|
|
||||||
|
Note: Destroying an publication also makes use of this method, so you
|
||||||
|
must keep the info of that you are checking (publishing or destroying...)
|
||||||
|
In our case, destroy is 1-step action so this will no get called while
|
||||||
|
destroying...
|
||||||
|
'''
|
||||||
|
import random
|
||||||
|
self._number -= 1
|
||||||
|
# Serialization will take care of storing self._number
|
||||||
|
|
||||||
|
# One of every 10 calls
|
||||||
|
if random.randint(0, 9) == 9:
|
||||||
|
self._reason = _('Random integer was 9!!! :-)')
|
||||||
|
return State.ERROR
|
||||||
|
|
||||||
|
if self._number <= 0:
|
||||||
|
return State.FINISHED
|
||||||
|
else:
|
||||||
|
return State.RUNNING
|
||||||
|
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
'''
|
||||||
|
Invoked when Publication manager noticed that the publication has finished.
|
||||||
|
This give us the oportunity of cleaning up things (as stored vars, etc..),
|
||||||
|
or initialize variables that will be needed in a later phase (by deployed
|
||||||
|
services)
|
||||||
|
|
||||||
|
Returned value, if any, is ignored
|
||||||
|
'''
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
# Make simply a random string
|
||||||
|
self._name = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(10))
|
||||||
|
|
||||||
|
def reasonOfError(self):
|
||||||
|
'''
|
||||||
|
If a publication produces an error, here we must notify the reason why
|
||||||
|
it happened. This will be called just after publish or checkState
|
||||||
|
if they return State.ERROR
|
||||||
|
|
||||||
|
Returns an string, in our case, set at checkState
|
||||||
|
'''
|
||||||
|
return self._reason
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
'''
|
||||||
|
This is called once a publication is no more needed.
|
||||||
|
|
||||||
|
This method do whatever needed to clean up things, such as
|
||||||
|
removing created "external" data (environment gets cleaned by core),
|
||||||
|
etc..
|
||||||
|
|
||||||
|
The retunred value is the same as when publishing, State.RUNNING,
|
||||||
|
State.FINISHED or State.ERROR.
|
||||||
|
'''
|
||||||
|
self._name = ''
|
||||||
|
self._reason = '' # In fact, this is not needed, but cleaning up things... :-)
|
||||||
|
|
||||||
|
# We do not do anything else to destroy this instance of publication
|
||||||
|
return State.FINISHED
|
||||||
|
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
'''
|
||||||
|
Invoked for canceling the current operation.
|
||||||
|
This can be invoked directly by an administration or by the clean up
|
||||||
|
of the deployed service (indirectly).
|
||||||
|
When administrator requests it, the cancel is "delayed" and not
|
||||||
|
invoked directly.
|
||||||
|
|
||||||
|
Also, take into account that cancel is the initiation of, maybe, a
|
||||||
|
multiple-step action, so it returns, as publish and destroy does.
|
||||||
|
|
||||||
|
In our case, cancel simply invokes "destroy", that cleans up
|
||||||
|
things and returns that the action has finished in 1 step.
|
||||||
|
'''
|
||||||
|
return self.destroy()
|
||||||
|
|
||||||
|
# Here ends the publication needed methods.
|
||||||
|
# Methods provided below are specific for this publication
|
||||||
|
# and will be used by user deployments that uses this kind of publication
|
||||||
|
|
||||||
|
def getBaseName(self):
|
||||||
|
'''
|
||||||
|
This sample method (just for this sample publication), provides
|
||||||
|
the name generater for this publication. This is just a sample, and
|
||||||
|
this will do the work
|
||||||
|
'''
|
||||||
|
return self._name
|
44
server/src/uds/services/OVirt/__init__.py
Normal file
44
server/src/uds/services/OVirt/__init__.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012 Virtual Cable S.L.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
'''
|
||||||
|
Sample Service module.
|
||||||
|
|
||||||
|
This package simply shows how a new service can be implemented.
|
||||||
|
|
||||||
|
|
||||||
|
The first thing to do in every package that is a module is register the
|
||||||
|
class that is responsible of providing the module with the system.
|
||||||
|
|
||||||
|
For this, we must simply import the class at __init__, UDS will take care
|
||||||
|
of the rest
|
||||||
|
'''
|
||||||
|
|
||||||
|
from OVirtProvider import Provider
|
||||||
|
|
BIN
server/src/uds/services/OVirt/provider.png
Normal file
BIN
server/src/uds/services/OVirt/provider.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
BIN
server/src/uds/services/OVirt/service.png
Normal file
BIN
server/src/uds/services/OVirt/service.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Loading…
Reference in New Issue
Block a user