lxd/db/migration.go
func (c *Cluster) ImportPreClusteringData(dump *Dump) error {
	tx, err := c.db.Begin()
	if err != nil {
		return errors.Wrap(err, "failed to start cluster database transaction")
	}

	// Delete the default profile in the cluster database, which always
	// gets created no matter what.
	_, err = tx.Exec("DELETE FROM profiles WHERE id=1")
	if err != nil {
		tx.Rollback()
		return errors.Wrap(err, "failed to delete default profile")
	}

	for _, table := range preClusteringTables {
		logger.Debugf("Migrating data for table %s", table)

		for i, row := range dump.Data[table] {
			for i, element := range row {
				// Convert []byte columns to string. This is safe to do since
				// the pre-clustering schema only had TEXT fields and no BLOB.
				bytes, ok := element.([]byte)
				if ok {
					row[i] = string(bytes)
				}
			}
			columns := dump.Schema[table]

			nullNodeID := false // Whether node-related rows should have a NULL node ID
			appendNodeID := func() {
				columns = append(columns, "node_id")
				if nullNodeID {
					row = append(row, nil)
				} else {
					row = append(row, int64(1))
				}
			}

			switch table {
			case "config":
				// Don't migrate the core.https_address and maas.machine config keys,
				// which is node-specific and must remain in the node
				// database.
				keys := []string{"core.https_address", "maas.machine"}
				skip := false
				for i, column := range columns {
					value, ok := row[i].(string)
					if !ok {
						continue
					}
					if column == "key" && shared.StringInSlice(value, keys) {
						skip = true
					}
				}
				if skip {
					continue
				}
			case "containers":
				appendNodeID()
			case "networks_config":
				// The keys listed in NetworkNodeConfigKeys
				// are the only ones which are not global to the
				// cluster, so all other keys will have a NULL
				// node_id.
				index := 0
				for i, column := range columns {
					if column == "key" {
						index = i
						break
					}
				}
				key := row[index].(string)
				if !shared.StringInSlice(key, NetworkNodeConfigKeys) {
					nullNodeID = true
					break
				}
				appendNodeID()
			case "storage_pools_config":
				// The keys listed in StoragePoolNodeConfigKeys
				// are the only ones which are not global to the
				// cluster, so all other keys will have a NULL
				// node_id.
				index := 0
				for i, column := range columns {
					if column == "key" {
						index = i
						break
					}
				}
				key := row[index].(string)
				if !shared.StringInSlice(key, StoragePoolNodeConfigKeys) {
					nullNodeID = true
					break
				}
				appendNodeID()
			case "networks":
				fallthrough
			case "storage_pools":
				columns = append(columns, "state")
				row = append(row, storagePoolCreated)
			case "storage_volumes":
				appendNodeID()
			}

			if shared.StringInSlice(table, preClusteringTablesRequiringProjectID) {
				// These tables have a project_id reference in the new schema.
				columns = append(columns, "project_id")
				row = append(row, 1) // Reference the default project.
			}

			stmt := fmt.Sprintf("INSERT INTO %s(%s)", table, strings.Join(columns, ", "))
			stmt += fmt.Sprintf(" VALUES %s", query.Params(len(columns)))
			result, err := tx.Exec(stmt, row...)
			if err != nil {
				tx.Rollback()
				return errors.Wrapf(err, "failed to insert row %d into %s", i, table)
			}
			n, err := result.RowsAffected()
			if err != nil {
				tx.Rollback()
				return errors.Wrapf(err, "no result count for row %d of %s", i, table)
			}
			if n != 1 {
				tx.Rollback()
				return fmt.Errorf("could not insert %d int %s", i, table)
			}

			// Also insert the image ID to node ID association.
			if shared.StringInSlice(table, []string{"images", "networks", "storage_pools"}) {
				entity := table[:len(table)-1]
				importNodeAssociation(entity, columns, row, tx)
			}
		}
	}

	return tx.Commit()
}
func (c *Cluster) ImportPreClusteringData(dump *Dump) error {
	tx, err := c.db.Begin()
	if err != nil {
		return errors.Wrap(err, "failed to start cluster database transaction")
	}

	// Delete the default profile in the cluster database, which always
	// gets created no matter what.
	_, err = tx.Exec("DELETE FROM profiles WHERE id=1")
	if err != nil {
		tx.Rollback()
		return errors.Wrap(err, "failed to delete default profile")
	}

	for _, table := range preClusteringTables {
		logger.Debugf("Migrating data for table %s", table)

		for i, row := range dump.Data[table] {
			for i, element := range row {
				// Convert []byte columns to string. This is safe to do since
				// the pre-clustering schema only had TEXT fields and no BLOB.
				bytes, ok := element.([]byte)
				if ok {
					row[i] = string(bytes)
				}
			}
			columns := dump.Schema[table]

			nullNodeID := false // Whether node-related rows should have a NULL node ID
			appendNodeID := func() {
				columns = append(columns, "node_id")
				if nullNodeID {
					row = append(row, nil)
				} else {
					row = append(row, int64(1))
				}
			}

			switch table {
			case "config":
				// Don't migrate the core.https_address and maas.machine config keys,
				// which is node-specific and must remain in the node
				// database.
				keys := []string{"core.https_address", "maas.machine"}
				skip := false
				for i, column := range columns {
					value, ok := row[i].(string)
					if !ok {
						continue
					}
					if column == "key" && shared.StringInSlice(value, keys) {
						skip = true
					}
				}
				if skip {
					continue
				}
			case "containers":
				appendNodeID()
			case "networks_config":
				// The keys listed in NetworkNodeConfigKeys
				// are the only ones which are not global to the
				// cluster, so all other keys will have a NULL
				// node_id.
				index := 0
				for i, column := range columns {
					if column == "key" {
						index = i
						break
					}
				}
				key := row[index].(string)
				if !shared.StringInSlice(key, NetworkNodeConfigKeys) {
					nullNodeID = true
					break
				}
				appendNodeID()
			case "storage_pools_config":
				// The keys listed in StoragePoolNodeConfigKeys
				// are the only ones which are not global to the
				// cluster, so all other keys will have a NULL
				// node_id.
				index := 0
				for i, column := range columns {
					if column == "key" {
						index = i
						break
					}
				}
				key := row[index].(string)
				if !shared.StringInSlice(key, StoragePoolNodeConfigKeys) {
					nullNodeID = true
					break
				}
				appendNodeID()
			case "networks":
				fallthrough
			case "storage_pools":
				columns = append(columns, "state")
				row = append(row, storagePoolCreated)
			case "storage_volumes":
				appendNodeID()
			}

			if shared.StringInSlice(table, preClusteringTablesRequiringProjectID) {
				// These tables have a project_id reference in the new schema.
				columns = append(columns, "project_id")
				row = append(row, 1) // Reference the default project.
			}

			stmt := fmt.Sprintf("INSERT INTO %s(%s)", table, strings.Join(columns, ", "))
			stmt += fmt.Sprintf(" VALUES %s", query.Params(len(columns)))
			result, err := tx.Exec(stmt, row...)
			if err != nil {
				tx.Rollback()
				return errors.Wrapf(err, "failed to insert row %d into %s", i, table)
			}
			n, err := result.RowsAffected()
			if err != nil {
				tx.Rollback()
				return errors.Wrapf(err, "no result count for row %d of %s", i, table)
			}
			if n != 1 {
				tx.Rollback()
				return fmt.Errorf("could not insert %d int %s", i, table)
			}

			// Also insert the image ID to node ID association.
			if shared.StringInSlice(table, []string{"images", "networks", "storage_pools"}) {
				entity := table[:len(table)-1]
				importNodeAssociation(entity, columns, row, tx)
			}
		}
	}

	return tx.Commit()
}
func ( c * Cluster ) ImportPreClusteringData ( dump * Dump ) error { tx , err := c . db . Begin ( ) if err != nil { return errors . Wrap ( err , " " ) } // Delete the default profile in the cluster database, which always // gets created no matter what. _ , err = tx . Exec ( " " ) if err != nil { tx . Rollback ( ) return errors . Wrap ( err , " " ) } for _ , table := range preClusteringTables { logger . Debugf ( " " , table ) for i , row := range dump . Data [ table ] { for i , element := range row { // Convert []byte columns to string. This is safe to do since // the pre-clustering schema only had TEXT fields and no BLOB. bytes , ok := element . ( [ ] byte ) if ok { row [ i ] = string ( bytes ) } } columns := dump . Schema [ table ] nullNodeID := false // Whether node-related rows should have a NULL node ID appendNodeID := func ( ) { columns = append ( columns , " " ) if nullNodeID { row = append ( row , nil ) } else { row = append ( row , int64 ( 1 ) ) } } switch table { case " " : // Don't migrate the core.https_address and maas.machine config keys, // which is node-specific and must remain in the node // database. keys := [ ] string { " " , " " } skip := false for i , column := range columns { value , ok := row [ i ] . ( string ) if ! ok { continue } if column == " " && shared . StringInSlice ( value , keys ) { skip = true } } if skip { continue } case " " : appendNodeID ( ) case " " : // The keys listed in NetworkNodeConfigKeys // are the only ones which are not global to the // cluster, so all other keys will have a NULL // node_id. index := 0 for i , column := range columns { if column == " " { index = i break } } key := row [ index ] . ( string ) if ! shared . StringInSlice ( key , NetworkNodeConfigKeys ) { nullNodeID = true break } appendNodeID ( ) case " " : // The keys listed in StoragePoolNodeConfigKeys // are the only ones which are not global to the // cluster, so all other keys will have a NULL // node_id. index := 0 for i , column := range columns { if column == " " { index = i break } } key := row [ index ] . ( string ) if ! shared . StringInSlice ( key , StoragePoolNodeConfigKeys ) { nullNodeID = true break } appendNodeID ( ) case " " : fallthrough case " " : columns = append ( columns , " " ) row = append ( row , storagePoolCreated ) case " " : appendNodeID ( ) } if shared . StringInSlice ( table , preClusteringTablesRequiringProjectID ) { // These tables have a project_id reference in the new schema. columns = append ( columns , " " ) row = append ( row , 1 ) // Reference the default project. } stmt := fmt . Sprintf ( " " , table , strings . Join ( columns , " " ) ) stmt += fmt . Sprintf ( " " , query . Params ( len ( columns ) ) ) result , err := tx . Exec ( stmt , row ... ) if err != nil { tx . Rollback ( ) return errors . Wrapf ( err , " " , i , table ) } n , err := result . RowsAffected ( ) if err != nil { tx . Rollback ( ) return errors . Wrapf ( err , " " , i , table ) } if n != 1 { tx . Rollback ( ) return fmt . Errorf ( " " , i , table ) } // Also insert the image ID to node ID association. if shared . StringInSlice ( table , [ ] string { " " , " " , " " } ) { entity := table [ : len ( table ) - 1 ] importNodeAssociation ( entity , columns , row , tx ) } } } return tx . Commit ( ) }
// ImportPreClusteringData imports the data loaded with LoadPreClusteringData.
ImportPreClusteringData imports the data loaded with LoadPreClusteringData .