diff --git a/server/src/uds/migrations/0007_auto__add_field_config_long.py b/server/src/uds/migrations/0007_auto__add_field_config_long.py new file mode 100644 index 00000000..8950def6 --- /dev/null +++ b/server/src/uds/migrations/0007_auto__add_field_config_long.py @@ -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'] \ No newline at end of file diff --git a/server/src/uds/models.py b/server/src/uds/models.py index bcef42af..05f6d1bf 100644 --- a/server/src/uds/models.py +++ b/server/src/uds/models.py @@ -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: ''' diff --git a/server/src/uds/services/OVirt/OVirtLinkedDeployment.py b/server/src/uds/services/OVirt/OVirtLinkedDeployment.py new file mode 100644 index 00000000..26271350 --- /dev/null +++ b/server/src/uds/services/OVirt/OVirtLinkedDeployment.py @@ -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 + \ No newline at end of file diff --git a/server/src/uds/services/OVirt/OVirtLinkedService.py b/server/src/uds/services/OVirt/OVirtLinkedService.py new file mode 100644 index 00000000..00046b75 --- /dev/null +++ b/server/src/uds/services/OVirt/OVirtLinkedService.py @@ -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 diff --git a/server/src/uds/services/OVirt/OVirtProvider.py b/server/src/uds/services/OVirt/OVirtProvider.py new file mode 100644 index 00000000..9ab2befd --- /dev/null +++ b/server/src/uds/services/OVirt/OVirtProvider.py @@ -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 + diff --git a/server/src/uds/services/OVirt/OVirtPublication.py b/server/src/uds/services/OVirt/OVirtPublication.py new file mode 100644 index 00000000..747ab599 --- /dev/null +++ b/server/src/uds/services/OVirt/OVirtPublication.py @@ -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 diff --git a/server/src/uds/services/OVirt/__init__.py b/server/src/uds/services/OVirt/__init__.py new file mode 100644 index 00000000..9932ca49 --- /dev/null +++ b/server/src/uds/services/OVirt/__init__.py @@ -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 + diff --git a/server/src/uds/services/OVirt/provider.png b/server/src/uds/services/OVirt/provider.png new file mode 100644 index 00000000..2748fbca Binary files /dev/null and b/server/src/uds/services/OVirt/provider.png differ diff --git a/server/src/uds/services/OVirt/service.png b/server/src/uds/services/OVirt/service.png new file mode 100644 index 00000000..fc4282c7 Binary files /dev/null and b/server/src/uds/services/OVirt/service.png differ