Пример #1
0
    def post(self, namespace=None, key_name=None, key=None):
        if not namespace:
            return api_base.error(400, 'no namespace specified')

        with db.get_lock('namespace', None, 'all', op='Namespace update'):
            rec = db.get_namespace(namespace)
            if not rec:
                rec = {'name': namespace, 'keys': {}}

            # Allow shortcut of creating key at same time as the namespace
            if key_name:
                if not key:
                    return api_base.error(400, 'no key specified')
                if not isinstance(key, str):
                    # Must be a string to encode()
                    return api_base.error(400, 'key is not a string')
                if key_name == 'service_key':
                    return api_base.error(403, 'illegal key name')

                encoded = str(
                    base64.b64encode(
                        bcrypt.hashpw(key.encode('utf-8'), bcrypt.gensalt())),
                    'utf-8')
                rec['keys'][key_name] = encoded

            # Initialise metadata
            db.persist_metadata('namespace', namespace, {})
            db.persist_namespace(namespace, rec)

        return namespace
Пример #2
0
    def new(cls, interface_uuid, netdesc, instance_uuid, order):
        if 'macaddress' not in netdesc or not netdesc['macaddress']:
            possible_mac = util_network.random_macaddr()
            mac_iface = {'interface_uuid': interface_uuid}
            while not etcd.create('macaddress', None, possible_mac, mac_iface):
                possible_mac = util_network.random_macaddr()
            netdesc['macaddress'] = possible_mac

        if not interface_uuid:
            # uuid should only be specified in testing
            interface_uuid = str(uuid4())

        NetworkInterface._db_create(
            interface_uuid,
            {
                'network_uuid': netdesc['network_uuid'],
                'instance_uuid': instance_uuid,
                'macaddr': netdesc['macaddress'],
                'ipv4': netdesc['address'],
                'order': order,
                'model': netdesc['model'],

                'version': cls.current_version
            }
        )

        ni = NetworkInterface.from_db(interface_uuid)
        ni._db_set_attribute('floating', {'floating_address': None})
        ni.state = NetworkInterface.STATE_INITIAL

        # TODO(andy): Integrate metadata into each object type
        # Initialise metadata
        db.persist_metadata('networkinterface', interface_uuid, {})

        return ni
Пример #3
0
 def create_namespace(self, namespace, key_name, key):
     encoded = str(
         base64.b64encode(
             bcrypt.hashpw(key.encode('utf-8'), bcrypt.gensalt())), 'utf-8')
     rec = {'name': namespace, 'keys': {key_name: encoded}}
     db.persist_metadata('namespace', namespace, {})
     db.persist_namespace(namespace, rec)
Пример #4
0
    def post(self,
             netblock=None,
             provide_dhcp=None,
             provide_nat=None,
             name=None,
             namespace=None):
        try:
            ipaddress.ip_network(netblock)
        except ValueError as e:
            return error(400, 'cannot parse netblock: %s' % e)

        if not namespace:
            namespace = get_jwt_identity()

        # If accessing a foreign name namespace, we need to be an admin
        if get_jwt_identity() not in [namespace, 'system']:
            return error(
                401,
                'only admins can create resources in a different namespace')

        network = db.allocate_network(netblock, provide_dhcp, provide_nat,
                                      name, namespace)
        db.add_event('network', network['uuid'], 'api', 'create', None, None)

        # Networks should immediately appear on the network node
        with db.get_lock('sf/network/%s' % network['uuid'], ttl=900) as _:
            if config.parsed.get('NODE_IP') == config.parsed.get(
                    'NETWORK_NODE_IP'):
                n = net.from_db(network['uuid'])
                if not n:
                    LOG.info(
                        'network(%s): network not found, genuinely missing' %
                        network['uuid'])
                    return error(404, 'network not found')

                n.create()
                n.ensure_mesh()
            else:
                admin_token = util.get_api_token(
                    'http://%s:%d' % (config.parsed.get('NETWORK_NODE_IP'),
                                      config.parsed.get('API_PORT')),
                    namespace=namespace)
                requests.request('put', ('http://%s:%d/deploy_network_node' %
                                         (config.parsed.get('NETWORK_NODE_IP'),
                                          config.parsed.get('API_PORT'))),
                                 data=json.dumps({'uuid': network['uuid']}),
                                 headers={
                                     'Authorization': admin_token,
                                     'User-Agent': util.get_user_agent()
                                 })

            db.add_event('network', network['uuid'], 'api', 'created', None,
                         None)
            db.update_network_state(network['uuid'], 'created')

            # Initialise metadata
            db.persist_metadata('network', network['uuid'], {})

        return network
Пример #5
0
    def delete(self, namespace, key=None, value=None):
        if not key:
            return error(400, 'no key specified')

        with db.get_lock('metadata', 'namespace', namespace):
            md = db.get_metadata('namespace', namespace)
            if md is None or key not in md:
                return error(404, 'key not found')
            del md[key]
            db.persist_metadata('namespace', namespace, md)
Пример #6
0
    def delete(self, network_uuid=None, key=None, network_from_db=None):
        if not key:
            return error(400, 'no key specified')

        with db.get_lock('metadata', 'network', network_uuid):
            md = db.get_metadata('network', network_uuid)
            if md is None or key not in md:
                return error(404, 'key not found')
            del md[key]
            db.persist_metadata('network', network_uuid, md)
Пример #7
0
    def delete(self, instance_uuid=None, key=None, instance_from_db=None):
        if not key:
            return error(400, 'no key specified')

        with db.get_lock('sf/metadata/instance/%s' % instance_uuid) as _:
            md = db.get_metadata('instance', instance_uuid)
            if md is None or key not in md:
                return error(404, 'key not found')
            del md[key]
            db.persist_metadata('instance', instance_uuid, md)
Пример #8
0
def _metadata_putpost(meta_type, owner, key, value):
    if meta_type not in ['namespace', 'instance', 'network']:
        return error(500, 'invalid meta_type %s' % meta_type)
    if not key:
        return error(400, 'no key specified')
    if not value:
        return error(400, 'no value specified')

    with db.get_lock('metadata', meta_type, owner):
        md = db.get_metadata(meta_type, owner)
        if md is None:
            md = {}
        md[key] = value
        db.persist_metadata(meta_type, owner, md)
Пример #9
0
    def post(self,
             netblock=None,
             provide_dhcp=None,
             provide_nat=None,
             name=None,
             namespace=None):
        try:
            ipaddress.ip_network(netblock)
        except ValueError as e:
            return error(400, 'cannot parse netblock: %s' % e)

        if not namespace:
            namespace = get_jwt_identity()

        # If accessing a foreign name namespace, we need to be an admin
        if get_jwt_identity() not in [namespace, 'system']:
            return error(
                401,
                'only admins can create resources in a different namespace')

        network = db.allocate_network(netblock, provide_dhcp, provide_nat,
                                      name, namespace)
        db.add_event('network', network['uuid'], 'api', 'create', None, None)

        # Networks should immediately appear on the network node
        db.enqueue('networknode', {
            'type': 'deploy',
            'network_uuid': network['uuid']
        })

        db.add_event('network', network['uuid'], 'deploy', 'enqueued', None,
                     None)
        db.add_event('network', network['uuid'], 'api', 'created', None, None)
        db.update_network_state(network['uuid'], 'created')

        # Initialise metadata
        db.persist_metadata('network', network['uuid'], {})

        return network
Пример #10
0
    def new(cls, name, namespace, netblock, provide_dhcp=False,
            provide_nat=False, uuid=None, vxid=None):

        if not uuid:
            # uuid should only be specified in testing
            uuid = str(uuid4())

        if not vxid:
            vxid = Network.allocate_vxid(uuid)

        # Pre-create the IPManager
        IPManager.new(uuid, netblock)

        Network._db_create(
            uuid,
            {
                'vxid': vxid,
                'name': name,
                'namespace': namespace,
                'netblock': netblock,
                'provide_dhcp': provide_dhcp,
                'provide_nat': provide_nat,
                'version': cls.current_version
            }
        )

        n = Network.from_db(uuid)
        n.state = Network.STATE_INITIAL

        # Networks should immediately appear on the network node
        etcd.enqueue('networknode', DeployNetworkTask(uuid))

        # TODO(andy): Integrate metadata into each object type
        # Initialise metadata
        db.persist_metadata('network', uuid, {})

        return n
Пример #11
0
    def post(self,
             name=None,
             cpus=None,
             memory=None,
             network=None,
             disk=None,
             ssh_key=None,
             user_data=None,
             placed_on=None,
             namespace=None,
             instance_uuid=None,
             video=None):
        global SCHEDULER

        # Check that the instance name is safe for use as a DNS host name
        if name != re.sub(r'([^a-zA-Z0-9_\-])', '', name) or len(name) > 63:
            return error(400,
                         'instance name must be useable as a DNS host name')

        # Sanity check
        if not disk:
            return error(400, 'instance must specify at least one disk')
        for d in disk:
            if not isinstance(d, dict):
                return error(400,
                             'disk specification should contain JSON objects')

        if network:
            for n in network:
                if not isinstance(n, dict):
                    return error(
                        400,
                        'network specification should contain JSON objects')

                if 'network_uuid' not in n:
                    return error(
                        400, 'network specification is missing network_uuid')

        if not video:
            video = {'model': 'cirrus', 'memory': 16384}

        if not namespace:
            namespace = get_jwt_identity()

        # Only system can specify a uuid
        if instance_uuid and get_jwt_identity() != 'system':
            return error(401, 'only system can specify an instance uuid')

        # If accessing a foreign namespace, we need to be an admin
        if get_jwt_identity() not in [namespace, 'system']:
            return error(
                401,
                'only admins can create resources in a different namespace')

        # The instance needs to exist in the DB before network interfaces are created
        if not instance_uuid:
            instance_uuid = str(uuid.uuid4())
            db.add_event('instance', instance_uuid, 'uuid allocated', None,
                         None, None)

        # Create instance object
        instance = virt.from_db(instance_uuid)
        if instance:
            if get_jwt_identity() not in [
                    instance.db_entry['namespace'], 'system'
            ]:
                logutil.info([virt.ThinInstance(instance_uuid)],
                             'Instance not found, ownership test')
                return error(404, 'instance not found')

        if not instance:
            instance = virt.from_definition(uuid=instance_uuid,
                                            name=name,
                                            disks=disk,
                                            memory_mb=memory,
                                            vcpus=cpus,
                                            ssh_key=ssh_key,
                                            user_data=user_data,
                                            owner=namespace,
                                            video=video,
                                            requested_placement=placed_on)

        # Initialise metadata
        db.persist_metadata('instance', instance_uuid, {})

        # Allocate IP addresses
        order = 0
        if network:
            for netdesc in network:
                n = net.from_db(netdesc['network_uuid'])
                if not n:
                    db.enqueue_instance_delete(
                        config.parsed.get('NODE_NAME'), instance_uuid, 'error',
                        'missing network %s during IP allocation phase' %
                        netdesc['network_uuid'])
                    return error(
                        404, 'network %s not found' % netdesc['network_uuid'])

                with db.get_lock('ipmanager',
                                 None,
                                 netdesc['network_uuid'],
                                 ttl=120):
                    db.add_event('network', netdesc['network_uuid'],
                                 'allocate address', None, None, instance_uuid)
                    ipm = db.get_ipmanager(netdesc['network_uuid'])
                    if 'address' not in netdesc or not netdesc['address']:
                        netdesc['address'] = ipm.get_random_free_address()
                    else:
                        if not ipm.reserve(netdesc['address']):
                            db.enqueue_instance_delete(
                                config.parsed.get('NODE_NAME'), instance_uuid,
                                'error',
                                'failed to reserve an IP on network %s' %
                                netdesc['network_uuid'])
                            return error(
                                409, 'address %s in use' % netdesc['address'])

                    db.persist_ipmanager(netdesc['network_uuid'], ipm.save())

                if 'model' not in netdesc or not netdesc['model']:
                    netdesc['model'] = 'virtio'

                db.create_network_interface(str(uuid.uuid4()), netdesc,
                                            instance_uuid, order)

        if not SCHEDULER:
            SCHEDULER = scheduler.Scheduler()

        try:
            # Have we been placed?
            if not placed_on:
                candidates = SCHEDULER.place_instance(instance, network)
                placement = candidates[0]

            else:
                SCHEDULER.place_instance(instance,
                                         network,
                                         candidates=[placed_on])
                placement = placed_on

        except exceptions.LowResourceException as e:
            db.add_event('instance', instance_uuid, 'schedule', 'failed', None,
                         'insufficient resources: ' + str(e))
            db.enqueue_instance_delete(config.parsed.get('NODE_NAME'),
                                       instance_uuid, 'error',
                                       'scheduling failed')
            return error(507, str(e))

        except exceptions.CandidateNodeNotFoundException as e:
            db.add_event('instance', instance_uuid, 'schedule', 'failed', None,
                         'candidate node not found: ' + str(e))
            db.enqueue_instance_delete(config.get.parsed('NODE_NAME'),
                                       instance_uuid, 'error',
                                       'scheduling failed')
            return error(404, 'node not found: %s' % e)

        # Record placement
        db.place_instance(instance_uuid, placement)
        db.add_event('instance', instance_uuid, 'placement', None, None,
                     placement)

        # Create a queue entry for the instance start
        tasks = [{
            'type': 'instance_preflight',
            'instance_uuid': instance_uuid,
            'network': network
        }]
        for disk in instance.db_entry['block_devices']['devices']:
            if 'base' in disk and disk['base']:
                tasks.append({
                    'type': 'image_fetch',
                    'instance_uuid': instance_uuid,
                    'url': disk['base']
                })
        tasks.append({
            'type': 'instance_start',
            'instance_uuid': instance_uuid,
            'network': network
        })

        # Enqueue creation tasks on desired node task queue
        db.enqueue(placement, {'tasks': tasks})
        db.add_event('instance', instance_uuid, 'create', 'enqueued', None,
                     None)

        # Watch for a while and return results if things are fast, give up
        # after a while and just return the current state
        start_time = time.time()
        while time.time() - start_time < config.parsed.get('API_ASYNC_WAIT'):
            i = db.get_instance(instance_uuid)
            if i['state'] in ['created', 'deleted', 'error']:
                return i
            time.sleep(0.5)
        return i
Пример #12
0
    def post(self,
             name=None,
             cpus=None,
             memory=None,
             network=None,
             disk=None,
             ssh_key=None,
             user_data=None,
             placed_on=None,
             namespace=None,
             instance_uuid=None):
        global SCHEDULER

        # We need to sanitise the name so its safe for DNS
        name = re.sub(r'([^a-zA-Z0-9_\-])', '', name)

        if not namespace:
            namespace = get_jwt_identity()

        # If accessing a foreign namespace, we need to be an admin
        if get_jwt_identity() not in [namespace, 'system']:
            return error(
                401,
                'only admins can create resources in a different namespace')

        # The instance needs to exist in the DB before network interfaces are created
        if not instance_uuid:
            instance_uuid = str(uuid.uuid4())
            db.add_event('instance', instance_uuid, 'uuid allocated', None,
                         None, None)

        # Create instance object
        instance = virt.from_db(instance_uuid)
        if instance:
            if get_jwt_identity() not in [
                    instance.db_entry['namespace'], 'system'
            ]:
                LOG.info('instance(%s): instance not found, ownership test' %
                         instance_uuid)
                return error(404, 'instance not found')

        if not instance:
            instance = virt.from_definition(uuid=instance_uuid,
                                            name=name,
                                            disks=disk,
                                            memory_mb=memory,
                                            vcpus=cpus,
                                            ssh_key=ssh_key,
                                            user_data=user_data,
                                            owner=namespace)

        if not SCHEDULER:
            SCHEDULER = scheduler.Scheduler()

        # Have we been placed?
        if not placed_on:
            candidates = SCHEDULER.place_instance(instance, network)
            if len(candidates) == 0:
                db.add_event('instance', instance_uuid, 'schedule', 'failed',
                             None, 'insufficient resources')
                db.update_instance_state(instance_uuid, 'error')
                return error(507, 'insufficient capacity')

            placed_on = candidates[0]
            db.place_instance(instance_uuid, placed_on)
            db.add_event('instance', instance_uuid, 'placement', None, None,
                         placed_on)

        else:
            try:
                candidates = SCHEDULER.place_instance(instance,
                                                      network,
                                                      candidates=[placed_on])
                if len(candidates) == 0:
                    db.add_event('instance', instance_uuid, 'schedule',
                                 'failed', None, 'insufficient resources')
                    db.update_instance_state(instance_uuid, 'error')
                    return error(507, 'insufficient capacity')
            except scheduler.CandidateNodeNotFoundException as e:
                return error(404, 'node not found: %s' % e)

        # Have we been placed on a different node?
        if not placed_on == config.parsed.get('NODE_NAME'):
            body = flask_get_post_body()
            body['placed_on'] = placed_on
            body['instance_uuid'] = instance_uuid
            body['namespace'] = namespace

            token = util.get_api_token(
                'http://%s:%d' % (placed_on, config.parsed.get('API_PORT')),
                namespace=namespace)
            r = requests.request('POST',
                                 'http://%s:%d/instances' %
                                 (placed_on, config.parsed.get('API_PORT')),
                                 data=json.dumps(body),
                                 headers={
                                     'Authorization': token,
                                     'User-Agent': util.get_user_agent()
                                 })

            LOG.info('Returning proxied request: %d, %s' %
                     (r.status_code, r.text))
            resp = flask.Response(r.text, mimetype='application/json')
            resp.status_code = r.status_code
            return resp

        # Check we can get the required IPs
        nets = {}
        allocations = {}

        def error_with_cleanup(status_code, message):
            for network_uuid in allocations:
                n = net.from_db(network_uuid)
                for addr, _ in allocations[network_uuid]:
                    with db.get_lock('sf/ipmanager/%s' % n.uuid, ttl=120) as _:
                        ipm = db.get_ipmanager(n.uuid)
                        ipm.release(addr)
                        db.persist_ipmanager(n.uuid, ipm.save())
            return error(status_code, message)

        order = 0
        if network:
            for netdesc in network:
                if 'network_uuid' not in netdesc or not netdesc['network_uuid']:
                    return error_with_cleanup(404, 'network not specified')

                if netdesc['network_uuid'] not in nets:
                    n = net.from_db(netdesc['network_uuid'])
                    if not n:
                        return error_with_cleanup(
                            404,
                            'network %s not found' % netdesc['network_uuid'])
                    nets[netdesc['network_uuid']] = n
                    n.create()

                with db.get_lock('sf/ipmanager/%s' % netdesc['network_uuid'],
                                 ttl=120) as _:
                    db.add_event('network', netdesc['network_uuid'],
                                 'allocate address', None, None, instance_uuid)
                    allocations.setdefault(netdesc['network_uuid'], [])
                    ipm = db.get_ipmanager(netdesc['network_uuid'])
                    if 'address' not in netdesc or not netdesc['address']:
                        netdesc['address'] = ipm.get_random_free_address()
                    else:
                        if not ipm.reserve(netdesc['address']):
                            return error_with_cleanup(
                                409, 'address %s in use' % netdesc['address'])
                    db.persist_ipmanager(netdesc['network_uuid'], ipm.save())
                    allocations[netdesc['network_uuid']].append(
                        (netdesc['address'], order))

                if 'model' not in netdesc or not netdesc['model']:
                    netdesc['model'] = 'virtio'

                db.create_network_interface(str(uuid.uuid4()), netdesc,
                                            instance_uuid, order)

                order += 1

        # Initialise metadata
        db.persist_metadata('instance', instance_uuid, {})

        # Now we can start the instance
        with db.get_lock('sf/instance/%s' % instance.db_entry['uuid'],
                         ttl=900) as lock:
            with util.RecordedOperation('ensure networks exist',
                                        instance) as _:
                for network_uuid in nets:
                    n = nets[network_uuid]
                    n.ensure_mesh()
                    n.update_dhcp()

            with util.RecordedOperation('instance creation', instance) as _:
                instance.create(lock=lock)

            for iface in db.get_instance_interfaces(instance.db_entry['uuid']):
                db.update_network_interface_state(iface['uuid'], 'created')

            return db.get_instance(instance_uuid)
Пример #13
0
 def persist_metadata(self, dbo_type, uuid, metadata):
     """Set metadata for a specified object"""
     if not DatabaseBackedObject.__subclasscheck__(dbo_type):
         raise NotImplementedError(
             'Objects must be subclasses of DatabaseBackedObject')
     db.persist_metadata(dbo_type.object_type, uuid, metadata)