Ejemplo n.º 1
0
def reject(id, status_text=None):
    """Rejects a request"""
    sysrequest = get_request_by_id(id)

    #check if system is already approved
    if sysrequest['status'] == 2:
        raise Exception('Request already approved')
    elif sysrequest['status'] == 1:
        raise Exception('Request already rejected')

    stmt = 'UPDATE `system_request` SET `status`=1, `updated_at`=NOW(), `updated_who`=%s, `status_text`=%s WHERE `id`=%s'
    params = (session['username'], status_text, id)

    curd = g.db.cursor(mysql.cursors.DictCursor)
    curd.execute(stmt, params)
    g.db.commit()
    flash('Request has been rejected', 'alert-info')

    #email requester
    subject = 'System request rejected'
    message = (
        'Your request for a system has been rejected.\n' + '\n' +
        'Request id: ' + str(sysrequest['id']) + '\n' + 'Requested at: ' +
        str(sysrequest['request_date']) + '\n' + 'Reason: ' +
        str(status_text) + '\n' + '\n' +
        'For more details see https://cortex.soton.ac.uk/sysrequest/view/' +
        str(sysrequest['id']))
    corpus = Corpus(g.db, app.config)
    corpus.send_email(str(sysrequest['requested_who']), subject, message)
Ejemplo n.º 2
0
def get_system_by_name(name,
                       must_have_vmware_uuid=False,
                       must_have_snow_sys_id=False):
    """Gets all the information about a system by its hostname."""

    corpus = Corpus(g.db, app.config)
    return corpus.get_system_by_name(name, must_have_vmware_uuid,
                                     must_have_snow_sys_id)
Ejemplo n.º 3
0
def get_vm_by_system_id(id):
    query = 'SELECT `vmware_uuid`, `vmware_vcenter` FROM `systems_info_view` WHERE `id`=%s AND `vmware_uuid` IS NOT NULL'
    params = (id, )
    curd = g.db.cursor(mysql.cursors.DictCursor)
    curd.execute(query, params)
    row = curd.fetchone()
    if row is None:
        raise ValueError
    corpus = Corpus(g.db, app.config)
    return corpus.vmware_get_vm_by_uuid(row['vmware_uuid'],
                                        row['vmware_vcenter'])
Ejemplo n.º 4
0
def api_installer_notify():
    """API endpoint to allow the bonemeal installer to notify cortex that the
	the installation is now complete and is about to reboot."""

    if 'uuid' not in request.form:
        app.logger.warn('Missing \'uuid\' parameter in installer notify API')
        abort(401)

    # Create a corpus (task helper) object
    corpus = Corpus(g.db, app.config)

    # VMware UUID based authentication
    system = cortex.lib.systems.get_system_by_vmware_uuid(
        request.form['uuid'].lower())

    if not system:
        app.logger.warn(
            'Could not match VMware UUID to a system for the installer notify API (UUID given: '
            + request.form['uuid'].lower() + ')')
        abort(404)

    # Mark as done
    if 'warnings' in request.form and int(request.form['warnings']) > 0:
        corpus.redis_set_vm_data("notify",
                                 "done-with-warnings",
                                 uuid=system['vmware_uuid'])
    else:
        corpus.redis_set_vm_data("notify", "done", uuid=system['vmware_uuid'])

    return "OK"
Ejemplo n.º 5
0
def generate_repeatable_password(id):
    corpus = Corpus(g.db, app.config)
    return corpus.system_get_repeatable_password(id)
Ejemplo n.º 6
0
def get_system_by_id(id):
    """Gets all the information about a system by its database ID."""

    corpus = Corpus(g.db, app.config)
    return corpus.get_system_by_id(id)
Ejemplo n.º 7
0
def decom_step2(id):
    # in this step we work out what steps to perform
    # then we load this into a list of steps, each step being a dictionary
    # this is used on the page to list the steps to the user
    # the list is also used to generate a JSON document which we sign using
    # app.config['SECRET_KEY'] and then send that onto the page as well.

    # load the corpus library
    corpus = Corpus(g.db, app.config)

    system = cortex.lib.systems.get_system_by_id(id)
    if system is None:
        abort(404)

    actions = []

    systemenv = None
    ## Find the environment that this VM is in based off of the CMDB env
    if 'cmdb_environment' in system:
        if system['cmdb_environment'] is not None:
            for env in app.config['ENVIRONMENTS']:
                if env['name'] == system['cmdb_environment']:
                    # We found the environment matching the system
                    systemenv = env
                    break

    ## Is the system linked to vmware?
    if 'vmware_uuid' in system:
        if system['vmware_uuid'] is not None:
            if len(system['vmware_uuid']) > 0:
                ## The system is linked to vmware - e.g. a VM exists

                vmobj = corpus.vmware_get_vm_by_uuid(system['vmware_uuid'],
                                                     system['vmware_vcenter'])

                if vmobj:
                    if vmobj.runtime.powerState == vim.VirtualMachine.PowerState.poweredOn:
                        actions.append({
                            'id':
                            'vm.poweroff',
                            'desc':
                            'Power off the virtual machine ' + system['name'],
                            'detail':
                            'UUID ' + system['vmware_uuid'] + ' on ' +
                            system['vmware_vcenter'],
                            'data': {
                                'uuid': system['vmware_uuid'],
                                'vcenter': system['vmware_vcenter']
                            }
                        })

                    actions.append({
                        'id':
                        'vm.delete',
                        'desc':
                        'Delete the virtual machine ' + system['name'],
                        'detail':
                        ' UUID ' + system['vmware_uuid'] + ' on ' +
                        system['vmware_vcenter'],
                        'data': {
                            'uuid': system['vmware_uuid'],
                            'vcenter': system['vmware_vcenter']
                        }
                    })

    ## Is the system linked to service now?
    if 'cmdb_id' in system:
        if system['cmdb_id'] is not None:
            if len(system['cmdb_id']) > 0:

                if system['cmdb_is_virtual']:
                    if system['cmdb_operational_status'] != u'Deleted':
                        actions.append({
                            'id':
                            'cmdb.update',
                            'desc':
                            'Mark the system as Deleted in the CMDB',
                            'detail':
                            system['cmdb_id'] + " on " + app.config['SN_HOST'],
                            'data':
                            system['cmdb_id']
                        })
                else:
                    if system['cmdb_operational_status'] != u'Decommissioned':
                        actions.append({
                            'id':
                            'cmdb.update',
                            'desc':
                            'Mark the system as Decommissioned in the CMDB',
                            'detail':
                            system['cmdb_id'] + " on " + app.config['SN_HOST'],
                            'data':
                            system['cmdb_id']
                        })

    ## Ask infoblox if a DNS host object exists for the name of the system
    try:
        refs = corpus.infoblox_get_host_refs(system['name'] + ".soton.ac.uk")

        if refs is not None:
            for ref in refs:
                actions.append({
                    'id':
                    'dns.delete',
                    'desc':
                    'Delete the DNS record ' + ref.split(':')[-1],
                    'detail':
                    'Delete the name ' + system['name'] +
                    '.soton.ac.uk - Infoblox reference: ' + ref,
                    'data':
                    ref
                })

    except Exception as ex:
        flash(
            "Warning - An error occured when communicating with Infoblox: " +
            str(type(ex)) + " - " + str(ex), "alert-warning")

    ## Check if a puppet record exists
    if 'puppet_certname' in system:
        if system['puppet_certname'] is not None:
            if len(system['puppet_certname']) > 0:
                actions.append({
                    'id':
                    'puppet.cortex.delete',
                    'desc':
                    'Delete the Puppet ENC configuration',
                    'detail':
                    system['puppet_certname'] + ' on ' + request.url_root,
                    'data':
                    system['id']
                })
                actions.append({
                    'id':
                    'puppet.master.delete',
                    'desc':
                    'Delete the system from the Puppet Master',
                    'detail':
                    system['puppet_certname'] + ' on ' +
                    app.config['PUPPET_MASTER'],
                    'data':
                    system['puppet_certname']
                })

    ## Check if TSM backups exist
    try:
        tsm_clients = corpus.tsm_get_system(system['name'])
        #if the TSM client is not decomissioned, then decomission it
        for client in tsm_clients:
            if client['DECOMMISSIONED'] is None:
                actions.append({
                    'id':
                    'tsm.decom',
                    'desc':
                    'Decommission the system in TSM',
                    'detail':
                    'Node ' + client['NAME'] + ' on server ' +
                    client['SERVER'],
                    'data': {
                        'NAME': client['NAME'],
                        'SERVER': client['SERVER']
                    }
                })
    except requests.exceptions.HTTPError as e:
        flash(
            "Warning - An error occured when communicating with TSM: " +
            str(ex), "alert-warning")
    except LookupError:
        pass
    except Exception as ex:
        flash(
            "Warning - An error occured when communicating with TSM: " +
            str(ex), "alert-warning")

    # We need to check all (unique) AD domains as we register development
    # Linux boxes to the production domain
    tested_domains = set()
    for adenv in app.config['WINRPC']:
        try:
            # If we've not tested this CortexWindowsRPC host before
            if app.config['WINRPC'][adenv]['host'] not in tested_domains:
                # Add it to the set of tested hosts
                tested_domains.update([app.config['WINRPC'][adenv]['host']])

                # If an AD object exists, append an action to delete it from that environment
                if corpus.windows_computer_object_exists(
                        adenv, system['name']):
                    actions.append({
                        'id':
                        'ad.delete',
                        'desc':
                        'Delete the Active Directory computer object',
                        'detail':
                        system['name'] + ' on domain ' +
                        app.config['WINRPC'][adenv]['domain'],
                        'data': {
                            'hostname': system['name'],
                            'env': adenv
                        }
                    })

        except Exception as ex:
            flash(
                "Warning - An error occured when communicating with Active Directory: "
                + str(type(ex)) + " - " + str(ex), "alert-warning")

    ## Work out the URL for any RHN systems
    rhnurl = app.config['RHN5_URL']
    if not rhnurl.endswith("/"):
        rhnurl = rhnurl + "/"
    rhnurl = rhnurl + "rhn/systems/details/Overview.do?sid="

    ## Check if a record exists in RHN for this system
    try:
        (rhn, key) = corpus.rhn5_connect()
        rsystems = rhn.system.search.hostname(key, system['name'])
        if len(rsystems) > 0:
            for rsys in rsystems:
                actions.append({
                    'id':
                    'rhn5.delete',
                    'desc':
                    'Delete the RHN Satellite object',
                    'detail':
                    rsys['name'] + ', RHN ID <a target="_blank" href="' +
                    rhnurl + str(rsys['id']) + '">' + str(rsys['id']) + "</a>",
                    'data': {
                        'id': rsys['id']
                    }
                })
    except Exception as ex:
        flash(
            "Warning - An error occured when communicating with RHN: " +
            str(ex), "alert-warning")

    # If there are actions to be performed, add on an action to raise a ticket to ESM (but not for Sandbox!)
    if len(actions) > 0 and system['class'] != "play":
        actions.append({
            'id':
            'ticket.ops',
            'desc':
            'Raises a ticket with operations to perform manual steps, such as removal from monitoring',
            'detail':
            'Creates a ticket in ServiceNow and assigns it to ' +
            workflow.config['TICKET_TEAM'],
            'data': {
                'hostname': system['name']
            }
        })

    # Turn the actions list into a signed JSON document via itsdangerous
    signer = JSONWebSignatureSerializer(app.config['SECRET_KEY'])
    json_data = signer.dumps(actions)

    return workflow.render_template("step2.html",
                                    actions=actions,
                                    system=system,
                                    json_data=json_data,
                                    title="Decommission Node")
Ejemplo n.º 8
0
    def get(self, host):

        corpus = Corpus(g.db, app.config)
        return corpus.dns_lookup(host)
Ejemplo n.º 9
0
def api_register_system():
    """API endpoint for when systems register with Cortex to obtain their
	   Puppet certificates, their Puppet environment, a satellite registration
	   key, etc. Clients can authenticate either via username/password, which
	   is checked against LDAP, or via the VMware virtual machine UUID, which
	   is checked against the VMware systems cache."""

    # Create a corpus (task helper) object
    corpus = Corpus(g.db, app.config)

    # Clients can send hostname, username and password (interactive installation)
    if 'hostname' in request.form and 'username' in request.form and 'password' in request.form:

        # Get the hostname and remove the domain portions, if any
        # we want the 'short' hostname / the node name
        hostname = cortex.lib.core.fqdn_strip_domain(request.form['hostname'])

        # Match the hostname to a system record in the database
        system = cortex.lib.systems.get_system_by_name(hostname)

        if not system:
            app.logger.warn(
                'Could not locate host in systems table for register API (hostname: '
                + hostname + ')')
            abort(404)

        # LDAP username/password authentication
        if not cortex.lib.user.authenticate(request.form['username'],
                                            request.form['password']):
            app.logger.warn('Incorrect username/password when registering ' +
                            hostname + ', username: '******'username'] + ')')
            abort(403)

        # LDAP authorisation
        if not cortex.lib.user.does_user_have_permission(
                'api.register', request.form['username']):
            app.logger.warn(
                'User does not have permission when attempting to register ' +
                hostname + ', username: '******'username'] + ')')
            abort(403)

        interactive = True

    # OR clients can send the vmware UUID as authentication instead (without a hostname)
    elif 'uuid' in request.form:
        # VMware UUID based authentication
        system = cortex.lib.systems.get_system_by_vmware_uuid(
            request.form['uuid'])

        if not system:
            app.logger.warn(
                'Could not match VMware UUID to a system for the register API (UUID given: '
                + request.form['uuid'].lower() + ')')
            abort(404)

        hostname = system['name']
        interactive = False

    else:
        app.logger.error(
            'Neither UUID or host+username+password sent to authenticate')
        abort(401)

    # Increment the build count - this is perhaps useful for tracking rebuilds,
    # but also means we can generate a new unique password for each register,
    # which means that a person can't find a VMs original password by running
    # the register API call again
    cortex.lib.systems.increment_build_count(system['id'])

    # Build the node's fqdn
    fqdn = hostname + '.soton.ac.uk'

    # Start building the response dictionary
    cdata = {}

    # Default to production environment (for CMDB / Satellite)
    cdata['environment'] = 'Production'

    # See if the system is linked to ServiceNow and we'll use that
    # environment. If it isn't linked then we can't do much, so just assume
    # production (as above)
    if 'cmdb_environment' in system and system['cmdb_environment']:
        cdata['environment'] = system['cmdb_environment']

    # Get the build identity from the request
    if 'ident' in request.form:
        ident = str(request.form['ident'])
    else:
        app.logger.warn('No build identity sent when attempting to register ' +
                        hostname)
        abort(400)

    # Check that we know about the build identity
    if ident not in app.config['REGISTER_ACTIONS']:
        app.logger.warn('Unknown build identity (' + ident +
                        ') sent when attempting to register ' + hostname)
        abort(400)

    # See if the build requires Puppet
    puppet_required = bool('puppet' in app.config['REGISTER_ACTIONS'][ident]
                           and app.config['REGISTER_ACTIONS'][ident]['puppet'])
    # See if the build requires Satellite
    satellite_required = bool(
        'satellite' in app.config['REGISTER_ACTIONS'][ident]
        and app.config['REGISTER_ACTIONS'][ident]['satellite'])
    # See if the build requires a random password
    password_required = bool(
        'password' in app.config['REGISTER_ACTIONS'][ident]
        and app.config['REGISTER_ACTIONS'][ident]['password'])

    if puppet_required:
        # Contact the cortex-puppet-bridge server to get ssl certificates for this hostname
        autosign_url = app.config['PUPPET_AUTOSIGN_URL']
        if not autosign_url.endswith('/'):
            autosign_url += '/'
        autosign_url += 'getcert/' + fqdn

        try:
            r = requests.get(
                autosign_url,
                headers={'X-Auth-Token': app.config['PUPPET_AUTOSIGN_KEY']},
                verify=app.config['PUPPET_AUTOSIGN_VERIFY'])
        except Exception as ex:
            app.logger.error(
                "Error occured contacting cortex-puppet-bridge server:" +
                str(ex))
            abort(500)

        if r.status_code == 200:
            try:
                pdata = r.json()
            except Exception as ex:
                app.logger.error(
                    "Error occured parsing response from cortex-puppet-bridge server:"
                    + str(ex))
                abort(500)

            for key in ['private_key', 'public_key', 'cert']:
                if not key in pdata:
                    app.logger.error(
                        "Error occured parsing response from cortex-puppet-bridge server. Parameter '"
                        + key + "' was not sent.")
                    abort(500)

                cdata[key] = pdata[key]
        else:
            app.logger.error(
                "Error occured contacting cortex-puppet-bridge server. HTTP status code: '"
                + str(r.status_code) + "'")
            abort(500)

    # Systems authenticating by UUID also want to know their hostname and
    # IP address in order to configure themselves!

    if not interactive:
        cdata['hostname'] = system['name']
        cdata['fqdn'] = fqdn
        netaddr = corpus.redis_get_vm_data("ipaddress",
                                           uuid=system['vmware_uuid'])
        netaddrv6 = corpus.redis_get_vm_data("ipv6address",
                                             uuid=system['vmware_uuid'])

        if netaddr:
            cdata['ipaddress'] = netaddr
        else:
            cdata['ipaddress'] = 'dhcp'

        if netaddrv6:
            cdata['ipv6address'] = netaddrv6

        # Add data for the data and swap disk
        # (names are reversed here because of Bonemeal)
        cdata['swap_disk'] = corpus.redis_get_vm_data(
            "disk_swap", uuid=system['vmware_uuid'])
        cdata['data_disk'] = corpus.redis_get_vm_data(
            "disk_data", uuid=system['vmware_uuid'])

        # Mark as done
        corpus.redis_set_vm_data("notify",
                                 "inprogress",
                                 uuid=system['vmware_uuid'])

    # Add on some other details which could be useful to post-install
    cdata['user'] = system['allocation_who']

    # If the build requires a random password

    if password_required:
        # This password is repeatable, but random so long as SECRET_KEY is not compromised
        cdata['password'] = cortex.lib.systems.generate_repeatable_password(
            system['id'])

    # Create puppet ENC entry if it does not already exist
    if puppet_required:
        cdata['certname'] = fqdn
        if 'puppet_certname' in system and system['puppet_certname'] is None:
            # A system record exists but no puppet_nodes entry. We'll create one!
            # We set the environment to what we determined above, which defaults
            # to production, but updates from the CMDB
            curd = g.db.cursor(mysql.cursors.DictCursor)
            curd.execute(
                "INSERT INTO `puppet_nodes` (`id`, `certname`, `env`) VALUES (%s, %s, %s)",
                (system['id'], fqdn, app.config['PUPPET_DEFAULT_ENVIRONMENT']))
            g.db.commit()
            app.logger.info('Created Puppet ENC entry for certname "' + fqdn +
                            '"')

    # Get the satellite registration key (if any)
    if satellite_required:
        if ident in app.config['SATELLITE_KEYS']:
            data = app.config['SATELLITE_KEYS'][ident]
            app.logger.warn("TESTING: USING ENV {} with data {}".format(
                cdata['environment'], data))
            if cdata['environment'] in data:
                cdata['satellite_activation_key'] = data[cdata['environment']]
            else:
                app.logger.warn(
                    'No Satellite activation key configured for OS ident, ' +
                    str(ident) + ' with environment ' + cdata['environment'] +
                    ' - a Satellite activation key will not be returned')
        else:
            app.logger.warn(
                'No Satellite activation keys configured for OS ident (' +
                str(ident) +
                ') - a Satellite activation key will not be returned')

    if interactive:
        cortex.lib.core.log(__name__,
                            "api.register.system",
                            "New system '" + fqdn +
                            "' registered via the API by " +
                            request.form['username'],
                            username=request.form['username'])
    else:
        cortex.lib.core.log(
            __name__, "api.register.system", "New system '" + fqdn +
            "' registered via the API by VM-UUID authentication")

    return jsonify(cdata)
Ejemplo n.º 10
0
def approve(id, status_text=None):
    """Approves and triggers the build of a system"""
    sysrequest = get_request_by_id(id)

    #check if system is already approved
    if sysrequest['status'] == 2:
        raise Exception('Request already approved')

    #load options from request
    stmt = ('SELECT `workflow`, '
            '`hostname`, '
            '`sockets`, '
            '`cores`, '
            '`ram`, '
            '`disk`, '
            '`template`, '
            '`network`, '
            '`cluster`, '
            '`environment`, '
            '`purpose`, '
            '`comments`, '
            '`expiry_date`, '
            '`sendmail` '
            'FROM `system_request` '
            'WHERE `id`=%s')
    params = (id, )
    curd = g.db.cursor(mysql.cursors.DictCursor)
    curd.execute(stmt, params)
    results = curd.fetchall()[0]

    options = {}
    options['workflow'] = results['workflow']
    options['sockets'] = results['sockets']
    options['cores'] = results['cores']
    options['ram'] = results['ram']
    options['disk'] = results['disk']
    options['template'] = results['template']
    options['network'] = results['network']
    options['cluster'] = results['cluster']
    options['env'] = results['environment']
    options['hostname'] = results['hostname']
    options['purpose'] = results['purpose']
    options['comments'] = results['comments']
    options['expiry'] = results['expiry_date']
    options['sendmail'] = results['sendmail']

    #trigger build

    options['wfconfig'] = app.workflows.get('buildvm').config

    # Connect to NeoCortex and start the task
    neocortex = cortex.lib.core.neocortex_connect()
    task_id = neocortex.create_task(
        'buildvm',
        session['username'],
        options,
        description="Creates and configures a virtual machine")

    #update db
    stmt = 'UPDATE `system_request` SET `status`=2, `updated_at`=NOW(), `updated_who`=%s, `status_text`=%s WHERE `id`=%s'
    params = (session['username'], status_text, id)

    curd.execute(stmt, params)
    g.db.commit()

    #email requester
    subject = 'System request approved'
    message = (
        'Your request for a system has been approved.\n' + '\n' +
        'Request id: ' + str(sysrequest['id']) + '\n' + 'Requested at: ' +
        str(sysrequest['request_date']) + '\n' + 'Reason: ' +
        str(status_text) + '\n' + '\n' +
        'For more details see https://cortex.soton.ac.uk/sysrequest/view/' +
        str(sysrequest['id']))
    corpus = Corpus(g.db, app.config)
    corpus.send_email(str(sysrequest['requested_who']), subject, message)

    return redirect(url_for('task_status', id=task_id))
Ejemplo n.º 11
0
def systems_add_existing():
	"""Handles the Add Existing System page, which can be used to add missing
	systems to Cortex"""

	# Check user permissions
	if not does_user_have_permission("systems.add_existing"):
		abort(403)

	# Get the list of enabled classes
	classes = cortex.lib.classes.list(hide_disabled=True)

	# Get the list of Puppet environments
	puppet_envs = cortex.lib.core.get_puppet_environments()

	# On GET requests, just show the form
	if request.method == 'GET':
		return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

	# On POST requests...
	elif request.method == 'POST':
		# Pull out information we always need
		if 'class' not in request.form or 'comment' not in request.form or 'env' not in request.form:
			flash('You must enter all the required information', category='alert-danger')
			return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

		class_name = request.form['class'].strip()
		comment = request.form['comment'].strip()
		puppet_env = request.form['env'].strip()

		# Validate the class
		if class_name != '_LEGACY' and class_name not in [c['name'] for c in classes]:
			flash('You must choose a valid class', category='alert-danger')
			return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

		# Validate the Puppet environment
		if len(puppet_env) != 0 and puppet_env not in [e['puppet'] for e in puppet_envs]:
			flash('You must select either no Puppet environment or a valid Puppet environment', category='alert-danger')
			return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

		# Pull out potentially optional information
		if class_name == '_LEGACY':
			if 'hostname' not in request.form:
				flash('You must enter a hostname', category='alert-danger')
				return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

			hostname = request.form['hostname'].strip()

			if len(hostname) == 0:
				flash('You must enter a hostname', category='alert-danger')
				return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")
		else:
			if 'number' not in request.form:
				flash('You must enter a server number', category='alert-danger')
				return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

			number = request.form['number'].strip()

			# Validate that we have a number
			if len(number) == 0:
				flash('You must enter a server number', category='alert-danger')
				return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

			number = int(number)

		# Get a cursor to the database
		curd = g.db.cursor(mysql.cursors.DictCursor)

		# For standard, non-legacy names...
		if class_name != '_LEGACY':
			# Get the class
			curd.execute("SELECT * FROM `classes` WHERE `name` = %s", (class_name,))
			class_data = curd.fetchone()

			# Ensure we have a class
			if class_data is None:
				flash('Invalid class selected', category='alert-danger')
				return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

			# Generate the name, padded out correctly
			corpus = Corpus(g.db, app.config)
			hostname = corpus.pad_system_name(class_name, number, class_data['digits'])

			# Type 0 (in the database) is a normal class-assigned system
			system_type = 0
		else:
			# We want NULLs in the database for these for legacy names
			number = None
			class_name = None

			# Type 1 (in the database) is a legacy system
			system_type = 1

		# Insert the system
		curd.execute("INSERT INTO `systems` (`type`, `class`, `number`, `name`, `allocation_date`, `allocation_who`, `allocation_comment`) VALUES (%s, %s, %s, %s, NOW(), %s, %s)", (system_type, class_name, number, hostname, session['username'], comment))
		system_id = curd.lastrowid

		# Insert a Puppet Node
		if len(puppet_env) > 0:
			curd.execute("INSERT INTO `puppet_nodes` (`id`, `certname`, `env`, `include_default`, `classes`, `variables`) VALUES (%s, %s, %s, %s, %s, %s)", (curd.lastrowid, hostname + ".soton.ac.uk", puppet_env, 1, "", ""))

		# Commit
		g.db.commit()

		# If we're linking to VMware
		if 'link_vmware' in request.form:
			# Search for a VM with the correct name		
			curd.execute("SELECT `uuid` FROM `vmware_cache_vm` WHERE `name` = %s", (hostname,))
			print curd._last_executed
			vm_results = curd.fetchall()

			if len(vm_results) == 0:
				flash("System not linked to VMware: Couldn't find a VM to link the system to", "alert-warning")
			elif len(vm_results) > 1:
				flash("System not linked to VMware: Found more than one VM matching the name", "alert-warning")
			else:
				curd.execute("UPDATE `systems` SET `vmware_uuid` = %s WHERE `id` = %s", (vm_results[0]['uuid'], system_id))
				g.db.commit()

		# If we're linking to ServiceNow
		if 'link_servicenow' in request.form:
			# Search for a CI with the correct name
			curd.execute("SELECT `sys_id` FROM `sncache_cmdb_ci` WHERE `name` = %s", (hostname,))
			print curd._last_executed
			ci_results = curd.fetchall()

			if len(ci_results) == 0:
				flash("System not linked to ServiceNow: Couldn't find a CI to link the system to", "alert-warning")
			elif len(ci_results) > 1:
				flash("System not linked to ServiceNow: Found more than one CI matching the name", "alert-warning")
			else:
				curd.execute("UPDATE `systems` SET `cmdb_id` = %s WHERE `id` = %s", (ci_results[0]['sys_id'], system_id))
				g.db.commit()

		cortex.lib.core.log(__name__, "systems.add.existing", "System manually added, id " + str(system_id),related_id=system_id)
		# Redirect to the system page for the system we just added
		flash("System added", "alert-success")
		return redirect(url_for('system', id=system_id))
Ejemplo n.º 12
0
def systems_add_existing():
	"""Handles the Add Existing System page, which can be used to add missing
	systems to Cortex"""

	# Check user permissions
	if not does_user_have_permission("systems.add_existing"):
		abort(403)

	# Get the list of enabled classes
	classes = cortex.lib.classes.list(hide_disabled=True)

	# Get the list of Puppet environments
	puppet_envs = cortex.lib.core.get_puppet_environments()

	# On GET requests, just show the form
	if request.method == 'GET':
		return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

	# On POST requests...
	elif request.method == 'POST':
		# Pull out information we always need
		if 'class' not in request.form or 'comment' not in request.form or 'env' not in request.form:
			flash('You must enter all the required information', category='alert-danger')
			return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

		class_name = request.form['class'].strip()
		comment = request.form['comment'].strip()
		puppet_env = request.form['env'].strip()

		# Validate the class
		if class_name != '_LEGACY' and class_name not in [c['name'] for c in classes]:
			flash('You must choose a valid class', category='alert-danger')
			return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

		# Validate the Puppet environment
		if len(puppet_env) != 0 and puppet_env not in [e['puppet'] for e in puppet_envs]:
			flash('You must select either no Puppet environment or a valid Puppet environment', category='alert-danger')
			return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

		# Pull out potentially optional information
		if class_name == '_LEGACY':
			if 'hostname' not in request.form:
				flash('You must enter a hostname', category='alert-danger')
				return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

			hostname = request.form['hostname'].strip()

			if len(hostname) == 0:
				flash('You must enter a hostname', category='alert-danger')
				return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")
		else:
			if 'number' not in request.form:
				flash('You must enter a server number', category='alert-danger')
				return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")
				
			number = request.form['number'].strip()

			# Validate that we have a number
			if len(number) == 0:
				flash('You must enter a server number', category='alert-danger')
				return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

			number = int(number)

		# Get a cursor to the database
		curd = g.db.cursor(mysql.cursors.DictCursor)

		# For standard, non-legacy names...
		if class_name != '_LEGACY':
			# Get the class
			curd.execute("SELECT * FROM `classes` WHERE `name` = %s", (class_name,))
			class_data = curd.fetchone()

			# Ensure we have a class
			if class_data is None:
				flash('Invalid class selected', category='alert-danger')
				return render_template('systems/add-existing.html', classes=classes, puppet_envs=puppet_envs, active='systems', title="Add existing system")

			# Generate the name, padded out correctly
			corpus = Corpus(g.db, app.config)
			hostname = corpus.pad_system_name(class_name, number, class_data['digits'])

			# Type 0 (in the database) is a normal class-assigned system
			system_type = 0
		else:
			# We want NULLs in the database for these for legacy names
			number = None
			class_name = None

			# Type 1 (in the database) is a legacy system
			system_type = 1
		
		# Insert the system
		curd.execute("INSERT INTO `systems` (`type`, `class`, `number`, `name`, `allocation_date`, `allocation_who`, `allocation_comment`) VALUES (%s, %s, %s, %s, NOW(), %s, %s)", (system_type, class_name, number, hostname, session['username'], comment))
		system_id = curd.lastrowid

		# Insert a Puppet Node
		if len(puppet_env) > 0:
			curd.execute("INSERT INTO `puppet_nodes` (`id`, `certname`, `env`, `include_default`, `classes`, `variables`) VALUES (%s, %s, %s, %s, %s, %s)", (curd.lastrowid, hostname + ".soton.ac.uk", puppet_env, 1, "", ""))

		# Commit
		g.db.commit()

		# If we're linking to VMware
		if 'link_vmware' in request.form:
			# Search for a VM with the correct name		
			curd.execute("SELECT `uuid` FROM `vmware_cache_vm` WHERE `name` = %s", (hostname,))
			print curd._last_executed
			vm_results = curd.fetchall()

			if len(vm_results) == 0:
				flash("System not linked to VMware: Couldn't find a VM to link the system to", "alert-warning")
			elif len(vm_results) > 1:
				flash("System not linked to VMware: Found more than one VM matching the name", "alert-warning")
			else:
				curd.execute("UPDATE `systems` SET `vmware_uuid` = %s WHERE `id` = %s", (vm_results[0]['uuid'], system_id))
				g.db.commit()

		# If we're linking to ServiceNow
		if 'link_servicenow' in request.form:
			# Search for a CI with the correct name
			curd.execute("SELECT `sys_id` FROM `sncache_cmdb_ci` WHERE `name` = %s", (hostname,))
			print curd._last_executed
			ci_results = curd.fetchall()

			if len(ci_results) == 0:
				flash("System not linked to ServiceNow: Couldn't find a CI to link the system to", "alert-warning")
			elif len(ci_results) > 1:
				flash("System not linked to ServiceNow: Found more than one CI matching the name", "alert-warning")
			else:
				curd.execute("UPDATE `systems` SET `cmdb_id` = %s WHERE `id` = %s", (ci_results[0]['sys_id'], system_id))
				g.db.commit()

		# Redirect to the system page for the system we just added
		flash("System added", "alert-success")
		return redirect(url_for('system', id=system_id))
Ejemplo n.º 13
0
def student():
    # Get the list of clusters
    clusters = cortex.lib.core.vmware_list_clusters(
        workflow.config['STU_VCENTER_TAG'])

    # Get the list of environments
    environments = cortex.lib.core.get_cmdb_environments()

    if request.method == 'GET':
        # Show form
        return workflow.render_template(
            "student.html",
            title="Create Virtual Machine",
            os_names=workflow.config['STU_OS_DISP_NAMES'],
            os_order=workflow.config['STU_OS_ORDER'])

    elif request.method == 'POST':
        # Ensure we have all parameters that we require
        if not {'template', 'network', 'expiry'}.issubset(request.form):
            flash('You must select options for all questions before creating',
                  'alert-danger')
            return redirect(url_for('student'))

        # Form validation
        try:
            try:
                # Extract all the parameters
                host_suffix = request.form['hostname']
                purpose = request.form['purpose']
                comments = request.form['comments']
                template = request.form['template']
                network = request.form['network']
                expiry = request.form['expiry']
                sendmail = 'send_mail' in request.form
            except KeyError as e:
                flash('Submitted data invalid ' + str(e), 'alert-danger')
                return redirect(url_for('student'))

            # Check name is RFC1123 complient
            if not re.compile(r"^[a-z0-9\-]{1,32}$").match(host_suffix):
                raise ValueError('Invalid hostname suffix')

            if not re.compile(r"^[a-z0-9\-]{1,16}$").match(
                    session['username']):
                raise Exception('Username contains incompatible characters')

            hostname = 'svm-' + session['username'] + '-' + host_suffix
            fqdn = hostname + '.ecs.soton.ac.uk'

            # load corpus
            corpus = Corpus(g.db, app.config)
            # ensure name is not in use
            if corpus.infoblox_get_host_refs(fqdn) is not None:
                raise ValueError(
                    'FQDN "' + fqdn +
                    '" appears to have zone records already. Try a different suffix."'
                )

            if template not in workflow.config['STU_OS_ORDER']:
                raise ValueError('Invalid image selected')

            if network not in workflow.config['STU_NETWORK_ORDER']:
                raise ValueError('Invalid network selected')

            expiry = datetime.datetime.strptime(expiry, '%Y-%m-%d')
            if expiry < datetime.datetime.utcnow():
                raise ValueError('Expiry date cannot be in the past')

        except ValueError as e:
            flash(str(e), 'alert-danger')
            return redirect(url_for('student'))

        # Build options to pass to the task
        options = {}
        options['workflow'] = 'student'
        options['sockets'] = workflow.config['STU_SPEC_SOCKETS']
        options['cores'] = workflow.config['STU_SPEC_CORES']
        options['ram'] = workflow.config['STU_SPEC_RAM']
        options['disk'] = workflow.config['STU_SPEC_DISK']
        options['template'] = template
        options['network'] = network
        options['cluster'] = workflow.confg['STU_CLUSTER']
        options['env'] = workflow.config['STU_ENV']
        options['hostname'] = hostname
        options['purpose'] = purpose
        options['comments'] = comments
        options['expiry'] = expiry
        options['sendmail'] = sendmail
        options['wfconfig'] = workflow.config

        # check if task is running
        try:
            curd = g.db.cursor(mysql.cursors.DictCursor)
            stmt = 'SELECT COUNT(*) AS `count` FROM `tasks` WHERE `username`=%s AND `status`=0'
            params = (session['username'], )
            curd.execute(stmt, params)
            tasks_in_progress = curd.fetchone()['count']
            if tasks_in_progress > 0:
                flash('You already have a task in progress', 'alert-warning')
                abort(400)

        except mysql.Error as e:
            flash(
                'An internal error occurred and your request could not be processed',
                'alert-warning')
            abort(500)

        # Check if manual approval is required
        ## They have too many VMs, the VM is to be public facing or is set to expire in over a year or a neocortex task is running
        # need some way to prevent creation spam where vms are requested faster than they are built
        if cortex.lib.systems.get_system_count(
                only_allocated_by=session['username']
        ) >= 3 or network == 'external' or expiry > datetime.datetime.utcnow(
        ) + datetime.timedelta(days=366):
            try:
                curd = g.db.cursor(mysql.cursors.DictCursor)
                sql = 'INSERT INTO `system_request` (`request_date`, `requested_who`, `hostname`, `workflow`, `sockets`, `cores`, `ram`, `disk`, `template`, `network`, `cluster`, `environment`, `purpose`, `comments`, `expiry_date`, `sendmail`, `status`, `updated_at`, `updated_who`) VALUES (NOW(), %s, %s ,%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), %s)'
                params = (session['username'], options['hostname'],
                          options['workflow'], options['sockets'],
                          options['cores'], options['ram'], options['disk'],
                          options['template'], options['network'],
                          options['cluster'], options['env'],
                          options['purpose'], options['comments'],
                          options['expiry'], options['sendmail'], 0,
                          session['username'])
                curd.execute(sql, params)

                g.db.commit()

                flash(
                    'Your request could not be completed automatically and is awaiting human approval',
                    'alert-warning')
            except mysql.Error as e:
                flash(
                    'An internal error occurred and your request could not be processed',
                    'alert-warning')

            return redirect(url_for('sysrequests'))

        try:
            curd = g.db.cursor(mysql.cursors.DictCursor)
            sql = 'INSERT INTO `system_request` (`request_date`, `requested_who`, `hostname`, `workflow`, `sockets`, `cores`, `ram`, `disk`, `template`, `network`, `cluster`, `environment`, `purpose`, `comments`, `expiry_date`, `sendmail`, `status`, `updated_at`, `updated_who`) VALUES (NOW(), %s, %s ,%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), %s)'
            params = (session['username'], options['hostname'],
                      options['workflow'], options['sockets'],
                      options['cores'], options['ram'], options['disk'],
                      options['template'], options['network'],
                      options['cluster'], options['env'], options['purpose'],
                      options['comments'], options['expiry'],
                      options['sendmail'], 2, session['username'])
            curd.execute(sql, params)

            g.db.commit()

            flash('Your request has been automatically approved',
                  'alert-success')
        except mysql.Error as e:
            flash(
                'An internal error occurred and your request could not be processed',
                'alert-warning')

        # Connect to NeoCortex and start the task
        neocortex = cortex.lib.core.neocortex_connect()
        task_id = neocortex.create_task(
            __name__,
            session['username'],
            options,
            description="Creates and configures a virtual machine")
        # Redirect to the status page for the task
        return redirect(url_for('task_status', id=task_id))