1
0
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:
Adolfo Gómez 2012-10-30 15:07:01 +00:00
parent a4c1f6af8f
commit 8a04db28e9
9 changed files with 1228 additions and 0 deletions

View 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']

View File

@ -1438,6 +1438,7 @@ class Config(models.Model):
key = models.CharField(max_length=64)
value = models.TextField(default = '')
crypt = models.BooleanField(default = False)
long = models.BooleanField(default = False)
class Meta:
'''

View 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

View 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

View 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

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB