Example #1
0
class LiveService(Service):
    """
    OpenStack Live Service
    """
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('OpenStack Live Volume')
    # : Type used internally to identify this provider
    typeType = 'openStackLiveService'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenStack live images based service')
    # : 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 _ (using ugettext_noop)
    iconFile = 'openstack.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 = True
    # : Tooltip shown to user when this item is pointed at admin interface, none
    # : because we don't use it
    cacheTooltip = _(
        'Number of desired machines to keep running waiting for an user')

    usesCache_L2 = False  # L2 Cache are running machines in suspended state
    cacheTooltip_L2 = _(
        'Number of desired machines to keep suspended waiting for use')
    # : If the service needs a s.o. manager (managers are related to agents
    # : provided by services itselfs, i.e. virtual machines with actors)
    needsManager = True
    # : If true, the system can't do an automatic assignation of a deployed user
    # : service from this service
    mustAssignManually = False
    canReset = True

    # : Types of publications (preparated data for deploys)
    # : In our case, we do no need a publication, so this is None
    publicationType = LivePublication
    # : Types of deploys (services in cache and/or assigned to users)
    deployedType = LiveDeployment

    allowedProtocols = protocols.GENERIC + (protocols.SPICE, )
    servicesTypeProvided = (serviceTypes.VDI, )

    # Now the form part
    region = gui.ChoiceField(label=_('Region'),
                             order=1,
                             tooltip=_('Service region'),
                             required=True,
                             rdonly=True)
    project = gui.ChoiceField(label=_('Project'),
                              order=2,
                              fills={
                                  'callbackName':
                                  'osFillResources',
                                  'function':
                                  helpers.getResources,
                                  'parameters':
                                  ['ov', 'ev', 'project', 'region', 'legacy']
                              },
                              tooltip=_('Project for this service'),
                              required=True,
                              rdonly=True)
    availabilityZone = gui.ChoiceField(
        label=_('Availability Zones'),
        order=3,
        fills={
            'callbackName':
            'osFillVolumees',
            'function':
            helpers.getVolumes,
            'parameters':
            ['ov', 'ev', 'project', 'region', 'availabilityZone', 'legacy']
        },
        tooltip=_('Service availability zones'),
        required=True,
        rdonly=True)
    volume = gui.ChoiceField(
        label=_('Volume'),
        order=4,
        tooltip=_('Base volume for service (restricted by availability zone)'),
        required=True,
        tab=_('Machine'))
    # volumeType = gui.ChoiceField(label=_('Volume Type'), order=5, tooltip=_('Volume type for service'), required=True)
    network = gui.ChoiceField(label=_('Network'),
                              order=6,
                              tooltip=_('Network to attach to this service'),
                              required=True,
                              tab=_('Machine'))
    flavor = gui.ChoiceField(label=_('Flavor'),
                             order=7,
                             tooltip=_('Flavor for service'),
                             required=True,
                             tab=_('Machine'))

    securityGroups = gui.MultiChoiceField(label=_('Security Groups'),
                                          order=8,
                                          tooltip=_('Service security groups'),
                                          required=True,
                                          tab=_('Machine'))

    baseName = gui.TextField(
        label=_('Machine Names'),
        rdonly=False,
        order=9,
        tooltip=_('Base name for clones from this machine'),
        required=True,
        tab=_('Machine'))

    lenName = gui.NumericField(
        length=1,
        label=_('Name Length'),
        defvalue=5,
        order=10,
        tooltip=_('Size of numeric part for the names of these machines'),
        required=True,
        tab=_('Machine'))

    ov = gui.HiddenField(value=None)
    ev = gui.HiddenField(value=None)
    legacy = gui.HiddenField(
        value=None
    )  # We need to keep the env so we can instantiate the Provider

    _api: typing.Optional['openstack.Client'] = None

    def initialize(self, values):
        """
        We check here form values to see if they are valid.

        Note that we check them through FROM variables, that already has been
        initialized by __init__ method of base class, before invoking this.
        """
        if values:
            tools.checkValidBasename(self.baseName.value, self.lenName.num())

        # self.ov.value = self.parent().serialize()
        # self.ev.value = self.parent().env.key

    def parent(self) -> 'Provider':
        return typing.cast('Provider', super().parent())

    def initGui(self):
        """
        Loads required values inside
        """
        api = self.parent().api()

        if not self.parent().legacy and self.parent().region.value:
            regions = [
                gui.choiceItem(self.parent().region.value,
                               self.parent().region.value)
            ]
        else:
            regions = [
                gui.choiceItem(r['id'], r['id']) for r in api.listRegions()
            ]

        self.region.setValues(regions)

        if not self.parent().legacy and self.parent().tenant.value:
            tenants = [
                gui.choiceItem(self.parent().tenant.value,
                               self.parent().tenant.value)
            ]
        else:
            tenants = [
                gui.choiceItem(t['id'], t['name']) for t in api.listProjects()
            ]
        self.project.setValues(tenants)

        # So we can instantiate parent to get API
        logger.debug(self.parent().serialize())

        self.ov.setDefValue(self.parent().serialize())
        self.ev.setDefValue(self.parent().env.key)
        self.legacy.setDefValue(self.parent().legacy and 'true' or 'false')

    @property
    def api(self) -> 'openstack.Client':
        if not self._api:
            self._api = self.parent().api(projectId=self.project.value,
                                          region=self.region.value)

        return self._api

    def sanitizeVmName(self, name: str) -> str:
        return self.parent().sanitizeVmName(name)

    def makeTemplate(self,
                     templateName: str,
                     description: typing.Optional[str] = None):
        # First, ensures that volume has not any running instances
        # if self.api.getVolume(self.volume.value)['status'] != 'available':
        #    raise Exception('The Volume is in use right now. Ensure that there is no machine running before publishing')

        description = description or 'UDS Template snapshot'
        return self.api.createVolumeSnapshot(self.volume.value, templateName,
                                             description)

    def getTemplate(self, snapshotId: str):
        """
        Checks current state of a template (an snapshot)
        """
        return self.api.getSnapshot(snapshotId)

    def deployFromTemplate(self, name: str, snapshotId: str) -> str:
        """
        Deploys a virtual machine on selected cluster from selected template

        Args:
            name: Name (sanitized) of the machine
            comments: Comments for machine
            snapshotId: Id of the snapshot to deploy from

        Returns:
            Id of the machine being created form template
        """
        logger.debug('Deploying from template %s machine %s', snapshotId, name)
        # self.datastoreHasSpace()
        return self.api.createServerFromSnapshot(
            snapshotId=snapshotId,
            name=name,
            availabilityZone=self.availabilityZone.value,
            flavorId=self.flavor.value,
            networkId=self.network.value,
            securityGroupsIdsList=self.securityGroups.value)['id']

    def removeTemplate(self, templateId: str) -> None:
        """
        invokes removeTemplate from parent provider
        """
        self.api.deleteSnapshot(templateId)

    def getMachineState(self, machineId: str) -> str:
        """
        Invokes getServer from openstack client

        Args:
            machineId: If of the machine to get state

        Returns:
            one of this values:
                ACTIVE. The server is active.
                BUILDING. The server has not finished the original build process.
                DELETED. The server is permanently deleted.
                ERROR. The server is in error.
                HARD_REBOOT. The server is hard rebooting. This is equivalent to pulling the power plug on a physical server, plugging it back in, and rebooting it.
                MIGRATING. The server is being migrated to a new host.
                PASSWORD. The password is being reset on the server.
                PAUSED. In a paused state, the state of the server is stored in RAM. A paused server continues to run in frozen state.
                REBOOT. The server is in a soft reboot state. A reboot command was passed to the operating system.
                REBUILD. The server is currently being rebuilt from an image.
                RESCUED. The server is in rescue mode. A rescue image is running with the original server image attached.
                RESIZED. Server is performing the differential copy of data that changed during its initial copy. Server is down for this stage.
                REVERT_RESIZE. The resize or migration of a server failed for some reason. The destination server is being cleaned up and the original source server is restarting.
                SOFT_DELETED. The server is marked as deleted but the disk images are still available to restore.
                STOPPED. The server is powered off and the disk image still persists.
                SUSPENDED. The server is suspended, either by request or necessity. This status appears for only the XenServer/XCP, KVM, and ESXi hypervisors. Administrative users can suspend an instance if it is infrequently used or to perform system maintenance. When you suspend an instance, its VM state is stored on disk, all memory is written to disk, and the virtual machine is stopped. Suspending an instance is similar to placing a device in hibernation; memory and vCPUs become available to create other instances.
                VERIFY_RESIZE. System is awaiting confirmation that the server is operational after a move or resize.
                SHUTOFF. The server was powered down by the user, either through the OpenStack Compute API or from within the server. For example, the user issued a shutdown -h command from within the server. If the OpenStack Compute manager detects that the VM was powered down, it transitions the server to the SHUTOFF status.
        """
        server = self.api.getServer(machineId)
        if server['status'] in ('ERROR', 'DELETED'):
            logger.warning('Got server status %s for %s: %s', server['status'],
                           machineId, server.get('fault'))
        return server['status']

    def startMachine(self, machineId: str) -> None:
        """
        Tries to start a machine. No check is done, it is simply requested to OpenStack.

        This start also "resume" suspended/paused machines

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.startServer(machineId)

    def stopMachine(self, machineId: str) -> None:
        """
        Tries to stop a machine. No check is done, it is simply requested to OpenStack

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.stopServer(machineId)

    def resetMachine(self, machineId: str) -> None:
        """
        Tries to stop a machine. No check is done, it is simply requested to OpenStack

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.resetServer(machineId)

    def suspendMachine(self, machineId: str) -> None:
        """
        Tries to suspend a machine. No check is done, it is simply requested to OpenStack

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.suspendServer(machineId)

    def resumeMachine(self, machineId: str) -> None:
        """
        Tries to start a machine. No check is done, it is simply requested to OpenStack

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.resumeServer(machineId)

    def removeMachine(self, machineId: str) -> None:
        """
        Tries to delete a machine. No check is done, it is simply requested to OpenStack

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.api.deleteServer(machineId)

    def getNetInfo(self, machineId: str) -> typing.Tuple[str, str]:
        """
        Gets the mac address of first nic of the machine
        """
        net = self.api.getServer(machineId)['addresses']
        vals = next(
            iter(net.values())
        )[0]  # Returns "any" mac address of any interface. We just need only one interface info
        # vals = six.next(six.itervalues(net))[0]
        return vals['OS-EXT-IPS-MAC:mac_addr'].upper(), vals['addr']

    def getBaseName(self) -> str:
        """
        Returns the base name
        """
        return self.baseName.value

    def getLenName(self) -> int:
        """
        Returns the length of numbers part
        """
        return int(self.lenName.value)
Example #2
0
class PoolPerformanceReport(StatsReport):
    filename = 'pools_performance.pdf'
    name = _('Pools performance by date')  # Report name
    description = _('Pools performance report by date')  # Report description
    uuid = '88932b48-1fd3-11e5-a776-10feed05884b'

    # Input fields
    pools = gui.MultiChoiceField(order=1,
                                 label=_('Pools'),
                                 tooltip=_('Pools for report'),
                                 required=True)

    startDate = gui.DateField(order=2,
                              label=_('Starting date'),
                              tooltip=_('starting date for report'),
                              defvalue=datetime.date.min,
                              required=True)

    endDate = gui.DateField(order=3,
                            label=_('Finish date'),
                            tooltip=_('finish date for report'),
                            defvalue=datetime.date.max,
                            required=True)

    samplingPoints = gui.NumericField(
        order=4,
        label=_('Number of intervals'),
        length=3,
        minValue=0,
        maxValue=32,
        tooltip=_('Number of sampling points used in charts'),
        defvalue='8')

    def initialize(self, values):
        pass

    def initGui(self):
        logger.debug('Initializing gui')
        vals = [
            gui.choiceItem(v.uuid, v.name)
            for v in ServicePool.objects.all().order_by('name')
        ]
        self.pools.setValues(vals)

    def getPools(self) -> typing.List[typing.Tuple[str, str]]:
        return [(v.id, v.name)
                for v in ServicePool.objects.filter(uuid__in=self.pools.value)]

    def getRangeData(self) -> typing.Tuple[str, typing.List, typing.List]:  # pylint: disable=too-many-locals
        start = self.startDate.stamp()
        end = self.endDate.stamp()

        if self.samplingPoints.num() < 2:
            self.samplingPoints.value = (self.endDate.date() -
                                         self.startDate.date()).days
        if self.samplingPoints.num() < 2:
            self.samplingPoints.value = 2
        if self.samplingPoints.num() > 32:
            self.samplingPoints.value = 32

        samplingPoints = self.samplingPoints.num()

        pools = self.getPools()

        if not pools:
            raise Exception(_('Select at least a service pool for the report'))

        logger.debug('Pools: %s', pools)

        # x axis label format
        if end - start > 3600 * 24 * 2:
            xLabelFormat = 'SHORT_DATE_FORMAT'
        else:
            xLabelFormat = 'SHORT_DATETIME_FORMAT'

        # Generate samplings interval
        samplingIntervals: typing.List[typing.Tuple[int, int]] = []
        prevVal = None
        for val in range(start, end, int(
            (end - start) / (samplingPoints + 1))):
            if prevVal is None:
                prevVal = val
                continue
            samplingIntervals.append((prevVal, val))
            prevVal = val

        # Store dataUsers for all pools
        poolsData = []

        fld = events.statsManager().getEventFldFor('username')

        reportData = []
        for p in pools:
            dataUsers = []
            dataAccesses = []
            for interval in samplingIntervals:
                key = (interval[0] + interval[1]) / 2
                q = events.statsManager().getEvents(
                    events.OT_DEPLOYED,
                    events.ET_ACCESS,
                    since=interval[0],
                    to=interval[1],
                    owner_id=p[0]).values(fld).annotate(cnt=Count(fld))
                accesses = 0
                for v in q:
                    accesses += v['cnt']

                dataUsers.append((key, len(q)))  # @UndefinedVariable
                dataAccesses.append((key, accesses))
                reportData.append({
                    'name':
                    p[1],
                    'date':
                    tools.timestampAsStr(interval[0], xLabelFormat) + ' - ' +
                    tools.timestampAsStr(interval[1], xLabelFormat),
                    'users':
                    len(q),
                    'accesses':
                    accesses
                })
            poolsData.append({
                'pool': p[0],
                'name': p[1],
                'dataUsers': dataUsers,
                'dataAccesses': dataAccesses,
            })

        return xLabelFormat, poolsData, reportData

    def generate(self):
        # Generate the sampling intervals and get dataUsers from db
        xLabelFormat, poolsData, reportData = self.getRangeData()

        graph1 = io.BytesIO()
        graph2 = io.BytesIO()

        # surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)  # @UndefinedVariable

        # logger.debug('PoolsData: %s', poolsData)

        X = [v[0] for v in poolsData[0]['dataUsers']]
        data = {
            'title':
            _('Distinct Users'),
            'x':
            X,
            'xtickFnc':
            lambda l: filters.date(datetime.datetime.fromtimestamp(X[int(l)]),
                                   xLabelFormat) if int(l) >= 0 else '',
            'xlabel':
            _('Date'),
            'y': [{
                'label': p['name'],
                'data': [v[1] for v in p['dataUsers']]
            } for p in poolsData],
            'ylabel':
            _('Users')
        }

        graphs.barChart(SIZE, data, graph1)

        X = [v[0] for v in poolsData[0]['dataAccesses']]
        data = {
            'title':
            _('Accesses'),
            'x':
            X,
            'xtickFnc':
            lambda l: filters.date(datetime.datetime.fromtimestamp(X[int(l)]),
                                   xLabelFormat) if int(l) >= 0 else '',
            'xlabel':
            _('Date'),
            'y': [{
                'label': p['name'],
                'data': [v[1] for v in p['dataAccesses']]
            } for p in poolsData],
            'ylabel':
            _('Accesses')
        }

        graphs.barChart(SIZE, data, graph2)

        # Generate Data for pools, basically joining all pool data

        return self.templateAsPDF(
            'uds/reports/stats/pools-performance.html',
            dct={
                'data': reportData,
                'pools': [i[1] for i in self.getPools()],
                'beginning': self.startDate.date(),
                'ending': self.endDate.date(),
                'intervals': self.samplingPoints.num(),
            },
            header=ugettext('UDS Pools Performance Report'),
            water=ugettext('Pools Performance'),
            images={
                'graph1': graph1.getvalue(),
                'graph2': graph2.getvalue()
            },
        )
class CountersPoolAssigned(StatsReport):
    filename = 'pools_counters.pdf'
    name = _('Pools usage on a day')  # Report name
    description = _(
        'Pools usage counters for an specific day')  # Report description
    uuid = '0b429f70-2fc6-11e7-9a2a-8fc37101e66a'

    startDate = gui.DateField(order=2,
                              label=_('Date'),
                              tooltip=_('Date for report'),
                              defvalue='',
                              required=True)

    pools = gui.MultiChoiceField(order=1,
                                 label=_('Pools'),
                                 tooltip=_('Pools for report'),
                                 required=True)

    def initialize(self, values):
        pass

    def initGui(self):
        logger.debug('Initializing gui')
        vals = [
            gui.choiceItem(v.uuid, v.name)
            for v in ServicePool.objects.all().order_by('name')
        ]
        self.pools.setValues(vals)

    def getData(self) -> typing.List[typing.Dict[str, typing.Any]]:
        # Generate the sampling intervals and get dataUsers from db
        start = self.startDate.date()
        end = self.startDate.date() + datetime.timedelta(days=1)

        data = []

        pool: ServicePool
        for poolUuid in self.pools.value:
            try:
                pool = ServicePool.objects.get(uuid=poolUuid)
            except Exception:
                continue

            hours = [0] * 24

            for x in counters.getCounters(pool,
                                          counters.CT_ASSIGNED,
                                          since=start,
                                          to=end,
                                          max_intervals=24,
                                          use_max=True,
                                          all=False):
                hour = x[0].hour
                val = int(x[1])
                if hours[hour] < val:
                    hours[hour] = val

            data.append({'uuid': pool.uuid, 'name': pool.name, 'hours': hours})

        logger.debug('data: %s', data)

        return data

    def generate(self):
        items = self.getData()

        graph1 = io.BytesIO()

        X = list(range(24))
        d = {
            'title':
            _('Services by hour'),
            'x':
            X,
            'xtickFnc':
            lambda xx: '{:02d}'.format(xx),  # pylint: disable=unnecessary-lambda
            'xlabel':
            _('Hour'),
            'y': [{
                'label': i['name'],
                'data': [i['hours'][v] for v in X]
            } for i in items],
            'ylabel':
            'Services'
        }

        graphs.barChart(SIZE, d, graph1)

        return self.templateAsPDF(
            'uds/reports/stats/pools-usage-day.html',
            dct={
                'data':
                items,
                'pools': [
                    v.name for v in ServicePool.objects.filter(
                        uuid__in=self.pools.value)
                ],
                'beginning':
                self.startDate.date(),
            },
            header=ugettext('Services usage report for a day'),
            water=ugettext('Service usage report'),
            images={'graph1': graph1.getvalue()},
        )
Example #4
0
class UsageByPool(StatsReport):
    filename = 'pools_usage.pdf'
    name = _('Pools usage by users')  # Report name
    description = _('Pools usage by user report')  # Report description
    uuid = '38ec12dc-beaf-11e5-bd0a-10feed05884b'

    # Input fields
    pool = gui.MultiChoiceField(order=1,
                                label=_('Pool'),
                                tooltip=_('Pool for report'),
                                required=True)

    startDate = gui.DateField(
        order=2,
        label=_('Starting date'),
        tooltip=_('starting date for report'),
        defvalue=datetime.date.min,
        required=True,
    )

    endDate = gui.DateField(
        order=3,
        label=_('Finish date'),
        tooltip=_('finish date for report'),
        defvalue=datetime.date.max,
        required=True,
    )

    def initialize(self, values):
        pass

    def initGui(self):
        logger.debug('Initializing gui')
        vals = [gui.choiceItem('0-0-0-0', ugettext('ALL POOLS'))] + [
            gui.choiceItem(v.uuid, v.name)
            for v in ServicePool.objects.all().order_by('name')
        ]
        self.pool.setValues(vals)

    def getData(
            self
    ) -> typing.Tuple[typing.List[typing.Dict[str, typing.Any]], str]:
        # Generate the sampling intervals and get dataUsers from db
        start = self.startDate.stamp()
        end = self.endDate.stamp()
        logger.debug(self.pool.value)
        if '0-0-0-0' in self.pool.value:
            pools = ServicePool.objects.all()
        else:
            pools = ServicePool.objects.filter(uuid__in=self.pool.value)
        data = []
        for pool in pools:
            items = (events.statsManager().getEvents(
                events.OT_DEPLOYED,
                (events.ET_LOGIN, events.ET_LOGOUT),
                owner_id=pool.id,
                since=start,
                to=end,
            ).order_by('stamp'))

            logins = {}
            for i in items:
                # if '\\' in i.fld1:
                #    continue

                if i.event_type == events.ET_LOGIN:
                    logins[i.fld4] = i.stamp
                else:
                    if i.fld4 in logins:
                        stamp = logins[i.fld4]
                        del logins[i.fld4]
                        total = i.stamp - stamp
                        data.append({
                            'name':
                            i.fld4,
                            'origin':
                            i.fld2.split(':')[0],
                            'date':
                            datetime.datetime.fromtimestamp(stamp),
                            'time':
                            total,
                            'pool':
                            pool.uuid,
                            'pool_name':
                            pool.name,
                        })

        return data, ','.join([p.name for p in pools])

    def generate(self):
        items, poolName = self.getData()

        return self.templateAsPDF(
            'uds/reports/stats/usage-by-pool.html',
            dct={
                'data': items,
                'pool': poolName,
            },
            header=ugettext('Users usage list'),
            water=ugettext('UDS Report of users usage'),
        )